diff --git a/.deploy/artwork-evolution-release/app/Http/Controllers/Web/SimilarArtworksPageController.php b/.deploy/artwork-evolution-release/app/Http/Controllers/Web/SimilarArtworksPageController.php index 10af6a56..f3fde437 100644 --- a/.deploy/artwork-evolution-release/app/Http/Controllers/Web/SimilarArtworksPageController.php +++ b/.deploy/artwork-evolution-release/app/Http/Controllers/Web/SimilarArtworksPageController.php @@ -102,7 +102,7 @@ final class SimilarArtworksPageController extends Controller 'page_title' => 'Similar to "' . $sourceTitle . '" — Skinbase', 'page_meta_description' => 'Discover artworks similar to "' . $sourceTitle . '" on Skinbase.', 'page_canonical' => $baseUrl, - 'page_robots' => 'noindex,follow', + 'page_robots' => 'index,follow', 'breadcrumbs' => collect([ (object) ['name' => 'Explore', 'url' => '/explore'], (object) ['name' => $sourceTitle, 'url' => $sourceUrl], diff --git a/app/Console/Commands/AcademyCoursesSyncFoundationsCommand.php b/app/Console/Commands/AcademyCoursesSyncFoundationsCommand.php new file mode 100644 index 00000000..4ea24383 --- /dev/null +++ b/app/Console/Commands/AcademyCoursesSyncFoundationsCommand.php @@ -0,0 +1,122 @@ +updateOrCreate( + ['slug' => 'ai-assisted-digital-art-foundations'], + [ + 'title' => 'AI-Assisted Digital Art Foundations', + 'subtitle' => 'A guided path through prompting, publishing, and better Skinbase-ready workflows.', + 'excerpt' => 'Learn the foundations of AI-assisted digital art, from better prompts and ethical rules to preparing, tagging, and publishing artwork on Skinbase.', + 'description' => 'A starter course for Skinbase creators who want a structured path from core AI-art concepts to cleaner publishing-ready results.', + 'access_level' => 'free', + 'difficulty' => 'beginner', + 'status' => 'published', + 'is_featured' => true, + 'order_num' => 1, + 'published_at' => now(), + ], + ); + + $sectionOrder = [ + 'Introduction', + 'Prompting Basics', + 'Publishing on Skinbase', + 'Workflow and Quality', + ]; + + $sections = collect($sectionOrder)->mapWithKeys(function (string $title, int $index) use ($course): array { + $section = AcademyCourseSection::query()->updateOrCreate( + ['course_id' => $course->id, 'slug' => Str::slug($title)], + [ + 'title' => $title, + 'order_num' => $index, + 'is_visible' => true, + ], + ); + + return [$title => $section]; + }); + + $lessonMap = [ + 'Introduction' => [ + 'what-is-ai-assisted-digital-art', + 'ai-ethics-and-skinbase-upload-rules', + 'ai-generated-vs-ai-assisted-artwork', + ], + 'Prompting Basics' => [ + 'prompting-basics-for-skinbase-creators', + 'how-to-write-better-wallpaper-prompts', + 'understanding-style-mood-lighting-and-composition', + ], + 'Publishing on Skinbase' => [ + 'how-to-prepare-ai-artwork-for-upload', + 'how-to-choose-better-tags-and-categories', + ], + 'Workflow and Quality' => [ + 'how-to-avoid-common-ai-image-problems', + 'from-idea-to-artwork-a-simple-skinbase-workflow', + ], + ]; + + $orderNum = 0; + foreach ($lessonMap as $sectionTitle => $slugs) { + $section = $sections->get($sectionTitle); + + foreach ($slugs as $slug) { + $lesson = AcademyLesson::query()->where('slug', $slug)->first(); + + if (! $lesson instanceof AcademyLesson) { + $this->warn(sprintf('Skipped missing lesson [%s].', $slug)); + continue; + } + + AcademyCourseLesson::query()->updateOrCreate( + [ + 'course_id' => $course->id, + 'lesson_id' => $lesson->id, + ], + [ + 'section_id' => $section?->id, + 'order_num' => $orderNum, + 'is_required' => true, + ], + ); + + $orderNum++; + } + } + + $course->forceFill([ + 'lessons_count_cache' => AcademyCourseLesson::query()->where('course_id', $course->id)->count(), + ])->save(); + + $this->cache->clearAll(); + $this->info('AI-Assisted Digital Art Foundations course synced.'); + + return self::SUCCESS; + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c79c1fa1..44dd39de 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,6 +11,7 @@ use App\Console\Commands\BackfillArtworkVectorIndexCommand; use App\Console\Commands\IndexArtworkVectorsCommand; use App\Console\Commands\SearchArtworkVectorsCommand; use App\Console\Commands\AggregateSimilarArtworkAnalyticsCommand; +use App\Console\Commands\AcademyCoursesSyncFoundationsCommand; use App\Console\Commands\AggregateFeedAnalyticsCommand; use App\Console\Commands\AggregateTagInteractionAnalyticsCommand; use App\Console\Commands\SeedTagInteractionDemoCommand; @@ -71,6 +72,7 @@ class Kernel extends ConsoleKernel ZipUnsupportedArtworkOriginalsCommand::class, SendTestMail::class, DispatchCollectionMaintenanceCommand::class, + AcademyCoursesSyncFoundationsCommand::class, BackfillArtworkEmbeddingsCommand::class, BackfillArtworkVectorIndexCommand::class, IndexArtworkVectorsCommand::class, diff --git a/app/Http/Controllers/Academy/AcademyCourseController.php b/app/Http/Controllers/Academy/AcademyCourseController.php new file mode 100644 index 00000000..f1fed55c --- /dev/null +++ b/app/Http/Controllers/Academy/AcademyCourseController.php @@ -0,0 +1,184 @@ +validate([ + 'difficulty' => ['nullable', 'string', 'max:40'], + 'access' => ['nullable', 'string', 'max:40'], + ]); + + $query = AcademyCourse::query()->published()->ordered(); + + if (filled($filters['difficulty'] ?? null)) { + $query->where('difficulty', $filters['difficulty']); + } + + if (filled($filters['access'] ?? null)) { + $query->where('access_level', $filters['access']); + } + + $courses = $query->paginate(12)->withQueryString(); + $courses->getCollection()->transform(function (AcademyCourse $course) use ($request): array { + return $this->access->coursePayload($course, $request->user(), [ + 'progress' => $this->progress->getProgress($request->user(), $course), + ]); + }); + + $featuredCourses = collect($this->cache->featuredCourses())->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user(), [ + 'progress' => $this->progress->getProgress($request->user(), $course), + ]))->values(); + + $seoCourses = $featuredCourses + ->concat(collect($courses->items())) + ->unique(fn (array $course): string => (string) ($course['slug'] ?? '')) + ->values(); + + $seo = app(SeoFactory::class) + ->academyCourseListingPage( + 'Academy Courses — Skinbase', + 'Follow guided Skinbase AI Academy courses built from reusable lessons, chapters, and creator workflows.', + route('academy.courses.index', $request->query()), + $seoCourses, + [ + ['name' => 'Academy', 'url' => route('academy.index')], + ['name' => 'Courses', 'url' => route('academy.courses.index')], + ], + ) + ->toArray(); + + return Inertia::render('Academy/CoursesIndex', [ + 'seo' => $seo, + 'title' => 'Academy courses', + 'description' => 'Guided learning paths built from reusable Academy lessons and creator workflows.', + 'items' => $courses, + 'featuredCourses' => $featuredCourses->all(), + 'filters' => $filters, + 'pricingUrl' => route('academy.pricing'), + ])->rootView('collections'); + } + + public function show(Request $request, AcademyCourse $course): Response + { + abort_unless((bool) config('academy.enabled', true), 404); + abort_unless($course->isPublished(), 404); + + $course->load(['sections', 'courseLessons.section', 'courseLessons.lesson.category']); + + $progress = $this->progress->getProgress($request->user(), $course); + $completedLessonIds = $request->user() ? $this->progress->getCompletedLessonIds($request->user(), $course) : []; + $orderedLessons = $this->navigation->orderedCourseLessons($course); + $stepMeta = $orderedLessons + ->values() + ->mapWithKeys(fn (AcademyCourseLesson $courseLesson, int $index): array => [ + $courseLesson->id => [ + 'course_step_number' => $index + 1, + 'course_step_label' => sprintf('Step %02d', $index + 1), + ], + ]); + $sections = $course->sections + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values() + ->map(function ($section) use ($completedLessonIds, $orderedLessons, $request, $stepMeta): array { + $sectionLessons = $orderedLessons + ->where('section_id', $section->id) + ->values() + ->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [ + 'completed_lesson_ids' => $completedLessonIds, + ...((array) $stepMeta->get($courseLesson->id, [])), + ])) + ->all(); + + return [ + 'id' => (int) $section->id, + 'title' => (string) $section->title, + 'slug' => (string) ($section->slug ?? ''), + 'description' => (string) ($section->description ?? ''), + 'order_num' => (int) ($section->order_num ?? 0), + 'is_visible' => (bool) ($section->is_visible ?? true), + 'lessons' => $sectionLessons, + ]; + }) + ->all(); + + $unsectionedLessons = $orderedLessons + ->whereNull('section_id') + ->values() + ->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [ + 'completed_lesson_ids' => $completedLessonIds, + ...((array) $stepMeta->get($courseLesson->id, [])), + ])) + ->all(); + + $coursePayload = $this->access->coursePayload($course, $request->user(), ['progress' => $progress]); + $courseKeywords = collect(explode(',', (string) ($course->meta_keywords ?? ''))) + ->map(fn (string $keyword): string => trim($keyword)) + ->filter() + ->values() + ->all(); + $courseImage = (string) ($coursePayload['cover_image_url'] ?? $coursePayload['teaser_image_url'] ?? $course->og_image ?? $course->cover_image ?? $course->teaser_image ?? ''); + + $seo = app(SeoFactory::class) + ->academyCoursePage( + (string) ($course->seo_title ?: ($course->title . ' — Skinbase Academy')), + (string) ($course->seo_description ?: $course->excerpt ?: 'Skinbase Academy course'), + route('academy.courses.show', ['course' => $course->slug]), + $courseImage, + [ + ['name' => 'Academy', 'url' => route('academy.index')], + ['name' => 'Courses', 'url' => route('academy.courses.index')], + ['name' => (string) $course->title, 'url' => route('academy.courses.show', ['course' => $course->slug])], + ], + $courseKeywords, + $course->published_at?->toAtomString(), + $course->updated_at?->toAtomString(), + (string) ($course->access_level ?? ''), + (string) ($course->difficulty ?? ''), + (int) ($course->estimated_minutes ?? 0), + $orderedLessons + ->values() + ->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [ + 'completed_lesson_ids' => $completedLessonIds, + ...((array) $stepMeta->get($courseLesson->id, [])), + ])) + ->all(), + ) + ->toArray(); + + return Inertia::render('Academy/CoursesShow', [ + 'seo' => $seo, + 'course' => $coursePayload, + 'sections' => $sections, + 'unsectionedLessons' => $unsectionedLessons, + 'pricingUrl' => route('academy.pricing'), + 'startUrl' => $request->user() ? route('academy.courses.start', ['course' => $course->slug]) : null, + ])->rootView('collections'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Academy/AcademyCourseEnrollmentController.php b/app/Http/Controllers/Academy/AcademyCourseEnrollmentController.php new file mode 100644 index 00000000..ea8993e4 --- /dev/null +++ b/app/Http/Controllers/Academy/AcademyCourseEnrollmentController.php @@ -0,0 +1,33 @@ +isPublished(), 404); + + $this->progress->markEnrollmentStarted($request->user(), $course); + $continueLesson = $this->progress->getContinueLesson($request->user(), $course); + + if ($continueLesson?->lesson) { + return redirect()->route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $continueLesson->lesson->slug]); + } + + return redirect()->route('academy.courses.show', ['course' => $course->slug]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Academy/AcademyCourseLessonController.php b/app/Http/Controllers/Academy/AcademyCourseLessonController.php new file mode 100644 index 00000000..824a16cb --- /dev/null +++ b/app/Http/Controllers/Academy/AcademyCourseLessonController.php @@ -0,0 +1,99 @@ +isPublished(), 404); + + $course->load(['sections', 'courseLessons.section', 'courseLessons.lesson.category']); + $courseLesson = $this->navigation->findCourseLesson($course, $lesson); + + abort_unless($courseLesson instanceof \App\Models\AcademyCourseLesson, 404); + + if ($request->user()) { + $this->progress->updateLastLesson($request->user(), $course, $lesson); + $this->progress->markCourseCompletedIfFinished($request->user(), $course); + } + + $progress = $this->progress->getProgress($request->user(), $course); + $previousLesson = $this->navigation->previousLesson($course, $lesson); + $nextLesson = $this->navigation->nextLesson($course, $lesson); + $courseOutline = $this->navigation->orderedCourseLessons($course) + ->map(fn (\App\Models\AcademyCourseLesson $entry): array => $this->access->courseLessonPayload($entry, $request->user())) + ->values() + ->all(); + + $payload = $this->access->courseLessonPayload($courseLesson, $request->user(), true); + $canonical = route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]); + $description = Str::limit(trim((string) ($lesson->seo_description ?? $lesson->excerpt ?? 'Skinbase Academy course lesson.')), 160, '...'); + $seo = app(SeoFactory::class)->academyLessonPage( + (string) ($lesson->seo_title ?? ($lesson->title . ' — ' . $course->title)), + $description, + $canonical, + (string) ($payload['article_cover_image_url'] ?? $payload['cover_image_url'] ?? $lesson->cover_image ?? ''), + [ + ['name' => 'Academy', 'url' => route('academy.index')], + ['name' => 'Courses', 'url' => route('academy.courses.index')], + ['name' => (string) $course->title, 'url' => route('academy.courses.show', ['course' => $course->slug])], + ['name' => (string) $lesson->title, 'url' => $canonical], + ], + array_values((array) ($payload['tags'] ?? [])), + $lesson->published_at?->toAtomString(), + $lesson->updated_at?->toAtomString(), + (string) $course->title, + )->toArray(); + + return Inertia::render('Academy/Show', [ + 'pageType' => 'lesson', + 'item' => $payload, + 'relatedLessons' => [], + 'relatedCourses' => [], + 'previousLesson' => $previousLesson ? $this->access->courseLessonPayload($previousLesson, $request->user()) : null, + 'nextLesson' => $nextLesson ? $this->access->courseLessonPayload($nextLesson, $request->user()) : null, + 'seo' => $seo, + 'pricingUrl' => route('academy.pricing'), + 'completeUrl' => $request->user() ? route('academy.lessons.complete', ['lesson' => $lesson->id]) : null, + 'completed' => $request->user()?->academyLessonProgress()->where('lesson_id', $lesson->id)->whereNotNull('completed_at')->exists() ?? false, + 'courseContext' => [ + 'id' => (int) $course->id, + 'title' => (string) $course->title, + 'slug' => (string) $course->slug, + 'subtitle' => (string) ($course->subtitle ?? ''), + 'showUrl' => route('academy.courses.show', ['course' => $course->slug]), + 'completePayload' => ['course_id' => $course->id], + 'progress' => [ + 'percent' => (int) ($progress['progress_percent'] ?? 0), + 'completedRequired' => (int) ($progress['completed_required'] ?? 0), + 'totalRequired' => (int) ($progress['total_required'] ?? 0), + 'completed' => (bool) ($progress['completed'] ?? false), + ], + 'outline' => $courseOutline, + ], + ])->rootView('collections'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Academy/AcademyHomeController.php b/app/Http/Controllers/Academy/AcademyHomeController.php index 087e0f64..6137f672 100644 --- a/app/Http/Controllers/Academy/AcademyHomeController.php +++ b/app/Http/Controllers/Academy/AcademyHomeController.php @@ -6,6 +6,7 @@ namespace App\Http\Controllers\Academy; use App\Http\Controllers\Controller; use App\Models\AcademyChallenge; +use App\Models\AcademyCourse; use App\Models\AcademyLesson; use App\Models\AcademyPromptTemplate; use App\Services\Academy\AcademyAccessService; @@ -41,11 +42,13 @@ final class AcademyHomeController extends Controller $home = $this->cache->homePayload(function (): array { return [ 'featuredLessons' => $this->cache->featuredLessons(), + 'featuredCourses' => $this->cache->featuredCourses(), 'featuredPrompts' => $this->cache->featuredPrompts(), 'featuredChallenges' => (bool) config('academy.challenges_enabled', true) ? $this->cache->featuredChallenges() : [], 'lessonCount' => AcademyLesson::query()->active()->published()->count(), + 'courseCount' => AcademyCourse::query()->published()->count(), 'promptCount' => AcademyPromptTemplate::query()->active()->published()->count(), 'challengeCount' => (bool) config('academy.challenges_enabled', true) ? AcademyChallenge::query()->publiclyVisible()->count() @@ -58,6 +61,7 @@ final class AcademyHomeController extends Controller 'pricingUrl' => route('academy.pricing'), 'links' => [ 'lessons' => route('academy.lessons.index'), + 'courses' => route('academy.courses.index'), 'prompts' => route('academy.prompts.index'), 'packs' => route('academy.packs.index'), 'challenges' => route('academy.challenges.index'), @@ -69,9 +73,11 @@ final class AcademyHomeController extends Controller ], 'stats' => [ 'lessonCount' => (int) $home['lessonCount'], + 'courseCount' => (int) $home['courseCount'], 'promptCount' => (int) $home['promptCount'], 'challengeCount' => (int) $home['challengeCount'], ], + 'featuredCourses' => collect($home['featuredCourses'])->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user()))->values()->all(), 'featuredLessons' => collect($home['featuredLessons'])->map(fn (AcademyLesson $lesson): array => $this->access->lessonPayload($lesson, $request->user()))->values()->all(), 'featuredPrompts' => collect($home['featuredPrompts'])->map(fn (AcademyPromptTemplate $prompt): array => $this->access->promptPayload($prompt, $request->user()))->values()->all(), 'featuredChallenges' => collect($home['featuredChallenges'])->map(fn (AcademyChallenge $challenge): array => $this->access->challengePayload($challenge, $request->user(), true))->values()->all(), diff --git a/app/Http/Controllers/Academy/AcademyLessonController.php b/app/Http/Controllers/Academy/AcademyLessonController.php index a2482741..1ecb40e0 100644 --- a/app/Http/Controllers/Academy/AcademyLessonController.php +++ b/app/Http/Controllers/Academy/AcademyLessonController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Http\Controllers\Academy; use App\Http\Controllers\Controller; +use App\Models\AcademyCourse; use App\Models\AcademyLesson; use App\Services\Academy\AcademyAccessService; use App\Services\Academy\AcademyCacheService; @@ -35,7 +36,7 @@ final class AcademyLessonController extends Controller ->with('category') ->active() ->published() - ->latest('published_at'); + ->orderedForCourse(); if (filled($filters['q'] ?? null)) { $query->where(function ($builder) use ($filters): void { @@ -87,33 +88,73 @@ final class AcademyLessonController extends Controller ->firstOrFail(); $payload = $this->access->lessonPayload($lesson, $request->user(), true); - $relatedLessons = $lesson->category_id !== null - ? AcademyLesson::query() - ->with('category') - ->active() - ->published() - ->where('category_id', $lesson->category_id) - ->where('id', '!=', $lesson->id) - ->orderByDesc('published_at') - ->limit(6) - ->get() - ->map(fn (AcademyLesson $relatedLesson): array => $this->access->lessonPayload($relatedLesson, $request->user())) - ->values() - ->all() - : []; + $courseQuery = AcademyLesson::query() + ->with('category') + ->active() + ->published(); + + if (filled($lesson->series_name)) { + $courseQuery->where('series_name', $lesson->series_name); + } elseif ($lesson->category_id !== null) { + $courseQuery->where('category_id', $lesson->category_id); + } else { + $courseQuery->whereKey($lesson->id); + } + + $courseLessons = $courseQuery + ->orderedForCourse() + ->get() + ->filter(fn (AcademyLesson $courseLesson): bool => $this->access->canAccessLesson($request->user(), $courseLesson)) + ->values(); + + $currentIndex = $courseLessons->search(fn (AcademyLesson $courseLesson): bool => $courseLesson->is($lesson)); + $previousLesson = is_int($currentIndex) && $currentIndex > 0 + ? $courseLessons->get($currentIndex - 1) + : null; + $nextLesson = is_int($currentIndex) && $currentIndex < ($courseLessons->count() - 1) + ? $courseLessons->get($currentIndex + 1) + : null; + + $relatedLessons = $courseLessons + ->reject(fn (AcademyLesson $courseLesson): bool => $courseLesson->is($lesson)) + ->take(6) + ->map(fn (AcademyLesson $relatedLesson): array => $this->access->lessonPayload($relatedLesson, $request->user())) + ->values() + ->all(); + $relatedCourses = AcademyCourse::query() + ->published() + ->ordered() + ->whereHas('courseLessons', fn ($builder) => $builder->where('lesson_id', $lesson->id)) + ->limit(3) + ->get() + ->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user())) + ->values() + ->all(); $canonical = route('academy.lessons.show', ['slug' => $lesson->slug]); $description = Str::limit(trim((string) ($lesson->seo_description ?? $lesson->excerpt ?? 'Skinbase Academy lesson.')), 160, '...'); - $seo = app(SeoFactory::class)->collectionPage( + $seo = app(SeoFactory::class)->academyLessonPage( (string) ($lesson->seo_title ?? ($lesson->title.' — Skinbase Academy')), $description, $canonical, - $lesson->cover_image, + (string) ($payload['article_cover_image_url'] ?? $payload['cover_image_url'] ?? $lesson->cover_image ?? ''), + [ + ['name' => 'Academy', 'url' => route('academy.index')], + ['name' => 'Lessons', 'url' => route('academy.lessons.index')], + ['name' => (string) $lesson->title, 'url' => $canonical], + ], + array_values((array) ($payload['tags'] ?? [])), + $lesson->published_at?->toAtomString(), + $lesson->updated_at?->toAtomString(), + (string) ($lesson->series_name ?: $lesson->category?->name ?: 'Academy'), )->toArray(); return Inertia::render('Academy/Show', [ 'pageType' => 'lesson', 'item' => $payload, 'relatedLessons' => $relatedLessons, + 'relatedCourses' => $relatedCourses, + 'previousLesson' => $previousLesson ? $this->access->lessonPayload($previousLesson, $request->user()) : null, + 'nextLesson' => $nextLesson ? $this->access->lessonPayload($nextLesson, $request->user()) : null, 'seo' => $seo, 'pricingUrl' => route('academy.pricing'), 'completeUrl' => $request->user() ? route('academy.lessons.complete', ['lesson' => $lesson->id]) : null, diff --git a/app/Http/Controllers/Academy/AcademyProgressController.php b/app/Http/Controllers/Academy/AcademyProgressController.php index 6cf0f7b5..c104dbf7 100644 --- a/app/Http/Controllers/Academy/AcademyProgressController.php +++ b/app/Http/Controllers/Academy/AcademyProgressController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Http\Controllers\Academy; use App\Http\Controllers\Controller; +use App\Models\AcademyCourse; use App\Models\AcademyLesson; use App\Services\Academy\AcademyAccessService; use App\Services\Academy\AcademyProgressService; @@ -24,7 +25,13 @@ final class AcademyProgressController extends Controller abort_unless((bool) config('academy.enabled', true), 404); abort_unless($this->access->canAccessLesson($request->user(), $lesson), 403); - $record = $this->progress->markLessonComplete($request->user(), $lesson); + $course = null; + + if ($request->filled('course_id')) { + $course = AcademyCourse::query()->published()->find($request->integer('course_id')); + } + + $record = $this->progress->markLessonComplete($request->user(), $lesson, $course); return response()->json([ 'ok' => true, diff --git a/app/Http/Controllers/Forum/ForumController.php b/app/Http/Controllers/Forum/ForumController.php index 4c7bce9c..8de480af 100644 --- a/app/Http/Controllers/Forum/ForumController.php +++ b/app/Http/Controllers/Forum/ForumController.php @@ -98,7 +98,7 @@ class ForumController extends Controller $thread->loadMissing([ 'category:id,name,slug', - 'user:id,name', + 'user:id,name,username', 'user.profile:user_id,avatar_hash', ]); @@ -116,7 +116,7 @@ class ForumController extends Controller $opPost = ForumPost::query() ->where('thread_id', $thread->id) ->with([ - 'user:id,name', + 'user:id,name,username', 'user.profile:user_id,avatar_hash', 'attachments:id,post_id,file_path,file_size,mime_type,width,height', ]) @@ -128,7 +128,7 @@ class ForumController extends Controller ->where('thread_id', $thread->id) ->when($opPost, fn ($query) => $query->where('id', '!=', $opPost->id)) ->with([ - 'user:id,name', + 'user:id,name,username', 'user.profile:user_id,avatar_hash', 'attachments:id,post_id,file_path,file_size,mime_type,width,height', ]) @@ -148,7 +148,7 @@ class ForumController extends Controller if ($quotePostId > 0) { $quotedPost = ForumPost::query() ->where('thread_id', $thread->id) - ->with('user:id,name') + ->with('user:id,name,username') ->find($quotePostId); } diff --git a/app/Http/Controllers/Settings/AcademyAdminController.php b/app/Http/Controllers/Settings/AcademyAdminController.php index f61079ca..c96cfff0 100644 --- a/app/Http/Controllers/Settings/AcademyAdminController.php +++ b/app/Http/Controllers/Settings/AcademyAdminController.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Academy\UpsertAcademyBadgeRequest; use App\Http\Requests\Academy\UpsertAcademyCategoryRequest; use App\Http\Requests\Academy\UpsertAcademyChallengeRequest; +use App\Http\Requests\Academy\UpsertAcademyCourseRequest; use App\Http\Requests\Academy\UpsertAcademyLessonRequest; use App\Http\Requests\Academy\UpsertAcademyPromptPackRequest; use App\Http\Requests\Academy\UpsertAcademyPromptTemplateRequest; @@ -16,17 +17,24 @@ use App\Models\AcademyBadge; use App\Models\AcademyCategory; use App\Models\AcademyChallenge; use App\Models\AcademyChallengeSubmission; +use App\Models\AcademyCourse; +use App\Models\AcademyCourseLesson; use App\Models\AcademyLesson; use App\Models\AcademyLessonBlock; +use App\Models\AcademyLessonRevision; use App\Models\AcademyPromptPack; use App\Models\AcademyPromptPackItem; use App\Models\AcademyPromptTemplate; +use App\Models\User; use App\Services\Academy\AcademyCacheService; +use App\Services\Academy\AcademyCourseLessonOrderingService; +use App\Services\Academy\AcademyLessonMarkdownRenderer; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -40,12 +48,17 @@ final class AcademyAdminController extends Controller private const PROMPT_PREVIEW_PREFIX = 'academy-prompts/previews'; - public function __construct(private readonly AcademyCacheService $cache) {} + public function __construct( + private readonly AcademyCacheService $cache, + private readonly AcademyCourseLessonOrderingService $courseLessonOrdering, + private readonly AcademyLessonMarkdownRenderer $lessonMarkdownRenderer, + ) {} public function dashboard(): Response { return Inertia::render('Admin/Academy/Dashboard', [ 'stats' => [ + 'courses' => AcademyCourse::query()->count(), 'lessons' => AcademyLesson::query()->count(), 'prompts' => AcademyPromptTemplate::query()->count(), 'packs' => AcademyPromptPack::query()->count(), @@ -57,6 +70,7 @@ final class AcademyAdminController extends Controller 'mrr' => 0, ], 'links' => [ + 'courses' => route('admin.academy.courses.index'), 'categories' => route('admin.academy.categories.index'), 'lessons' => route('admin.academy.lessons.index'), 'prompts' => route('admin.academy.prompts.index'), @@ -73,6 +87,48 @@ final class AcademyAdminController extends Controller return $this->renderIndex('categories'); } + public function coursesIndex(): Response + { + return $this->renderIndex('courses'); + } + + public function coursesCreate(): Response + { + return $this->renderForm('courses', new AcademyCourse); + } + + public function coursesStore(UpsertAcademyCourseRequest $request): RedirectResponse + { + $course = new AcademyCourse; + $course->fill($this->persistCourseAttributes($request))->save(); + $this->cache->clearAll(); + + return redirect()->route('admin.academy.courses.edit', ['academyCourse' => $course])->with('success', 'Academy course created.'); + } + + public function coursesEdit(AcademyCourse $academyCourse): Response + { + return $this->renderForm('courses', $academyCourse); + } + + public function coursesUpdate(UpsertAcademyCourseRequest $request, AcademyCourse $academyCourse): RedirectResponse + { + $academyCourse->fill($this->persistCourseAttributes($request, $academyCourse))->save(); + $this->cache->clearAll(); + + return redirect()->route('admin.academy.courses.edit', ['academyCourse' => $academyCourse])->with('success', 'Academy course updated.'); + } + + public function coursesDestroy(AcademyCourse $academyCourse): RedirectResponse + { + $this->deleteStoredLessonCoverIfLocal((string) $academyCourse->cover_image); + $this->deleteStoredLessonCoverIfLocal((string) $academyCourse->teaser_image); + $academyCourse->delete(); + $this->cache->clearAll(); + + return redirect()->route('admin.academy.courses.index')->with('success', 'Academy course deleted.'); + } + public function categoriesCreate(): Response { return $this->renderForm('categories', new AcademyCategory); @@ -135,6 +191,7 @@ final class AcademyAdminController extends Controller $lesson = DB::transaction(function () use ($request): AcademyLesson { $lesson = new AcademyLesson; $lesson->fill($this->persistLessonAttributes($request))->save(); + $this->syncLessonCourses($lesson, $request->validated('course_ids', [])); $this->syncLessonBlocks($lesson, $request->validated('blocks', [])); return $lesson; @@ -155,7 +212,9 @@ final class AcademyAdminController extends Controller public function lessonsUpdate(UpsertAcademyLessonRequest $request, AcademyLesson $academyLesson): RedirectResponse { DB::transaction(function () use ($request, $academyLesson): void { + $this->createLessonRevision($academyLesson, $request->user(), 'Before lesson update'); $academyLesson->fill($this->persistLessonAttributes($request, $academyLesson))->save(); + $this->syncLessonCourses($academyLesson, $request->validated('course_ids', [])); $this->syncLessonBlocks($academyLesson, $request->validated('blocks', [])); }); @@ -164,6 +223,32 @@ final class AcademyAdminController extends Controller return redirect()->route('admin.academy.lessons.edit', ['academyLesson' => $academyLesson])->with('success', 'Academy lesson updated.'); } + public function lessonsRestoreRevision(Request $request, AcademyLesson $academyLesson, AcademyLessonRevision $academyLessonRevision): RedirectResponse + { + abort_unless((int) $academyLessonRevision->lesson_id === (int) $academyLesson->id, 404); + + $validated = $request->validate([ + 'field' => ['nullable', 'string', 'in:category_id,title,slug,lesson_number,course_order,series_name,excerpt,content,difficulty,access_level,lesson_type,cover_image,article_cover_image,tags,video_url,reading_minutes,featured,active,published_at,seo_title,seo_description,course_ids,blocks'], + ]); + + $field = filled($validated['field'] ?? null) ? (string) $validated['field'] : null; + + DB::transaction(function () use ($academyLesson, $academyLessonRevision, $field, $request): void { + $note = $field + ? sprintf('Before restoring %s from revision #%d', $field, $academyLessonRevision->id) + : sprintf('Before restoring revision #%d', $academyLessonRevision->id); + + $this->createLessonRevision($academyLesson, $request->user(), $note); + $this->applyLessonRevisionSnapshot($academyLesson, $academyLessonRevision, $field); + }); + + $this->cache->clearAll(); + + return redirect()->route('admin.academy.lessons.edit', ['academyLesson' => $academyLesson])->with('success', $field + ? sprintf('Lesson field "%s" restored from revision.', $field) + : 'Lesson restored from revision.'); + } + public function lessonsDestroy(AcademyLesson $academyLesson): RedirectResponse { $academyLesson->load(['blocks.comparisonResults']); @@ -176,6 +261,7 @@ final class AcademyAdminController extends Controller } $this->deleteStoredLessonCoverIfLocal((string) $academyLesson->cover_image); + $this->deleteStoredLessonCoverIfLocal((string) $academyLesson->article_cover_image); $academyLesson->delete(); $this->cache->clearAll(); @@ -397,7 +483,13 @@ final class AcademyAdminController extends Controller private function renderIndex(string $resource): Response { $meta = $this->resourceMeta($resource); - $items = $meta['model']::query()->latest('updated_at')->paginate(25)->withQueryString(); + $query = $meta['model']::query()->latest('updated_at'); + + if ($resource === 'prompts') { + $query->with('category'); + } + + $items = $query->paginate(25)->withQueryString(); $items->getCollection()->transform(fn (Model $model): array => $this->serializeIndexItem($resource, $model)); return Inertia::render('Admin/Academy/CrudIndex', [ @@ -424,23 +516,75 @@ final class AcademyAdminController extends Controller 'indexUrl' => route($meta['route_base'].'.index'), 'destroyUrl' => $record->exists ? route($meta['route_base'].'.destroy', $this->routeParams($resource, $record)) : null, 'method' => $record->exists ? 'patch' : 'post', - 'editorContext' => $this->formEditorContext($resource), + 'editorContext' => $this->formEditorContext($resource, $record), ]); } - private function formEditorContext(string $resource): array + private function formEditorContext(string $resource, Model $record): array { + if ($resource === 'courses') { + return [ + 'links' => array_filter([ + 'builder' => $record->exists ? route('admin.academy.courses.builder.edit', ['academyCourse' => $record]) : null, + 'preview' => $record->exists ? route('academy.courses.show', ['course' => $record->slug]) : null, + ]), + 'coverUploadUrl' => route('api.studio.academy.lessons.media.upload'), + 'coverDeleteUrl' => route('api.studio.academy.lessons.media.destroy'), + 'bodyMediaUploadUrl' => route('api.studio.academy.lessons.media.upload'), + 'bodyMediaDeleteUrl' => route('api.studio.academy.lessons.media.destroy'), + 'bodyMediaAssetsUrl' => route('api.studio.academy.lessons.media.assets'), + 'coverCdnBaseUrl' => rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/'), + 'outlineSummary' => $record instanceof AcademyCourse && $record->exists + ? $this->serializeCourseOutlineSummary($record) + : null, + 'courseLessons' => $record instanceof AcademyCourse && $record->exists + ? $this->serializeCourseEditorLessons($record) + : [], + 'availableLessons' => $record instanceof AcademyCourse && $record->exists + ? $this->serializeCourseAvailableLessons($record) + : [], + 'attachLessonUrl' => $record instanceof AcademyCourse && $record->exists + ? route('admin.academy.courses.lessons.attach', ['academyCourse' => $record]) + : null, + 'reorderUrl' => $record instanceof AcademyCourse && $record->exists + ? route('admin.academy.courses.reorder', ['academyCourse' => $record]) + : null, + ]; + } + + if ($resource === 'prompts') { + return [ + 'links' => array_filter([ + 'preview' => $record->exists ? route('academy.prompts.show', ['slug' => $record->slug]) : null, + ]), + 'comparisonMediaUploadUrl' => route('api.studio.academy.lessons.media.upload'), + 'comparisonMediaDeleteUrl' => route('api.studio.academy.lessons.media.destroy'), + 'comparisonMediaAssetsUrl' => route('api.studio.academy.lessons.media.assets'), + 'comparisonMediaBaseUrl' => rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/'), + 'comparisonCodeLists' => [ + 'providers' => array_values(array_filter(array_map('strval', (array) config('academy.prompt_comparison.providers', [])))), + 'models' => array_values(array_filter(array_map('strval', (array) config('academy.prompt_comparison.models', [])))), + ], + ]; + } + if ($resource !== 'lessons') { return []; } return [ + 'currentLessonId' => $record instanceof AcademyLesson && $record->exists ? (int) $record->id : null, + 'currentLessonTitle' => $record instanceof AcademyLesson && $record->exists ? (string) $record->title : '', 'coverUploadUrl' => route('api.studio.academy.lessons.media.upload'), 'coverDeleteUrl' => route('api.studio.academy.lessons.media.destroy'), 'bodyMediaUploadUrl' => route('api.studio.academy.lessons.media.upload'), 'bodyMediaDeleteUrl' => route('api.studio.academy.lessons.media.destroy'), 'bodyMediaAssetsUrl' => route('api.studio.academy.lessons.media.assets'), 'coverCdnBaseUrl' => rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/'), + 'numbering' => $this->serializeLessonNumberingContext($record instanceof AcademyLesson ? $record : null), + 'revisions' => $record instanceof AcademyLesson && $record->exists + ? $this->serializeLessonRevisions($record) + : [], 'categories' => AcademyCategory::query() ->where('type', 'lesson') ->orderBy('order_num') @@ -449,6 +593,7 @@ final class AcademyAdminController extends Controller ->map(fn (AcademyCategory $category): array => $this->serializeCategoryOption($category)) ->values() ->all(), + 'courses' => $this->serializeLessonEditorCourses($record instanceof AcademyLesson ? $record : null), 'categoryStoreUrl' => route('api.academy.categories.store'), 'categoryManageUrl' => route('admin.academy.categories.index'), ]; @@ -457,6 +602,36 @@ final class AcademyAdminController extends Controller private function resourceMeta(string $resource): array { return match ($resource) { + 'courses' => [ + 'model' => AcademyCourse::class, + 'title' => 'Academy Courses', + 'singular' => 'course', + 'subtitle' => 'Build structured learning paths from reusable Academy lessons.', + 'route_base' => 'admin.academy.courses', + 'columns' => ['title', 'difficulty', 'access_level', 'status', 'is_featured'], + 'fields' => [ + ['name' => 'title', 'label' => 'Title', 'type' => 'text'], + ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], + ['name' => 'subtitle', 'label' => 'Subtitle', 'type' => 'text'], + ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea', 'rows' => 4], + ['name' => 'description', 'label' => 'Description', 'type' => 'textarea', 'rows' => 8], + ['name' => 'cover_image', 'label' => 'Cover Image', 'type' => 'text'], + ['name' => 'teaser_image', 'label' => 'Teaser Image', 'type' => 'text'], + ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->courseAccessOptions()], + ['name' => 'difficulty', 'label' => 'Difficulty', 'type' => 'select', 'options' => $this->courseDifficultyOptions()], + ['name' => 'status', 'label' => 'Status', 'type' => 'select', 'options' => $this->courseStatusOptions()], + ['name' => 'order_num', 'label' => 'Order', 'type' => 'number'], + ['name' => 'estimated_minutes', 'label' => 'Estimated Minutes', 'type' => 'number'], + ['name' => 'published_at', 'label' => 'Published At', 'type' => 'datetime-local'], + ['name' => 'seo_title', 'label' => 'SEO Title', 'type' => 'text'], + ['name' => 'seo_description', 'label' => 'SEO Description', 'type' => 'textarea', 'rows' => 4], + ['name' => 'meta_keywords', 'label' => 'Meta Keywords', 'type' => 'textarea', 'rows' => 3], + ['name' => 'og_title', 'label' => 'OpenGraph Title', 'type' => 'text'], + ['name' => 'og_description', 'label' => 'OpenGraph Description', 'type' => 'textarea', 'rows' => 4], + ['name' => 'og_image', 'label' => 'OpenGraph Image', 'type' => 'text'], + ['name' => 'is_featured', 'label' => 'Featured', 'type' => 'checkbox'], + ], + ], 'categories' => [ 'model' => AcademyCategory::class, 'title' => 'Academy Categories', @@ -483,6 +658,7 @@ final class AcademyAdminController extends Controller 'columns' => ['title', 'difficulty', 'access_level', 'featured', 'active'], 'fields' => [ ['name' => 'category_id', 'label' => 'Category', 'type' => 'select', 'options' => $this->categoryOptions('lesson')], + ['name' => 'course_ids', 'label' => 'Courses', 'type' => 'multiselect', 'options' => $this->courseOptions()], ['name' => 'title', 'label' => 'Title', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea'], @@ -491,6 +667,7 @@ final class AcademyAdminController extends Controller ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->accessOptions()], ['name' => 'lesson_type', 'label' => 'Lesson Type', 'type' => 'text'], ['name' => 'cover_image', 'label' => 'Cover Image', 'type' => 'text'], + ['name' => 'tags', 'label' => 'Tags', 'type' => 'csv'], ['name' => 'video_url', 'label' => 'Video URL', 'type' => 'text'], ['name' => 'reading_minutes', 'label' => 'Reading Minutes', 'type' => 'number'], ['name' => 'published_at', 'label' => 'Published At', 'type' => 'datetime-local'], @@ -604,6 +781,17 @@ final class AcademyAdminController extends Controller private function serializeIndexItem(string $resource, Model $model): array { return match ($resource) { + 'courses' => [ + 'id' => (int) $model->id, + 'title' => (string) $model->title, + 'difficulty' => (string) $model->difficulty, + 'access_level' => (string) $model->access_level, + 'status' => (string) $model->status, + 'is_featured' => (bool) $model->is_featured, + 'edit_url' => route('admin.academy.courses.edit', ['academyCourse' => $model]), + 'destroy_url' => route('admin.academy.courses.destroy', ['academyCourse' => $model]), + 'builder_url' => route('admin.academy.courses.builder.edit', ['academyCourse' => $model]), + ], 'categories' => [ 'id' => (int) $model->id, 'name' => (string) $model->name, @@ -626,10 +814,20 @@ final class AcademyAdminController extends Controller 'prompts' => [ 'id' => (int) $model->id, 'title' => (string) $model->title, + 'slug' => (string) $model->slug, + 'excerpt' => (string) ($model->excerpt ?? ''), 'difficulty' => (string) $model->difficulty, 'access_level' => (string) $model->access_level, + 'category_name' => (string) ($model->category?->name ?? ''), + 'aspect_ratio' => (string) ($model->aspect_ratio ?? ''), + 'featured' => (bool) $model->featured, 'prompt_of_week' => (bool) $model->prompt_of_week, 'active' => (bool) $model->active, + 'preview_image_url' => $this->resolvePromptPreviewImageUrl((string) ($model->preview_image ?? '')), + 'comparisons_count' => count($this->serializePromptToolNotes((array) ($model->tool_notes ?? []))), + 'tags' => array_values(array_filter(array_map(static fn ($tag): string => trim((string) $tag), (array) ($model->tags ?? [])))), + 'updated_at' => optional($model->updated_at)->toIso8601String(), + 'preview_url' => route('academy.prompts.show', ['slug' => $model->slug]), 'edit_url' => route('admin.academy.prompts.edit', ['academyPromptTemplate' => $model]), 'destroy_url' => route('admin.academy.prompts.destroy', ['academyPromptTemplate' => $model]), ], @@ -668,6 +866,30 @@ final class AcademyAdminController extends Controller private function serializeFormRecord(string $resource, Model $record): array { return match ($resource) { + 'courses' => [ + 'title' => (string) ($record->title ?? ''), + 'slug' => (string) ($record->slug ?? ''), + 'subtitle' => (string) ($record->subtitle ?? ''), + 'excerpt' => (string) ($record->excerpt ?? ''), + 'description' => (string) ($record->description ?? ''), + 'cover_image' => (string) ($record->cover_image ?? ''), + 'cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($record->cover_image ?? '')), + 'teaser_image' => (string) ($record->teaser_image ?? ''), + 'teaser_image_url' => $this->resolveLessonCoverImageUrl((string) ($record->teaser_image ?? '')), + 'access_level' => (string) ($record->access_level ?? 'free'), + 'difficulty' => (string) ($record->difficulty ?? 'beginner'), + 'status' => (string) ($record->status ?? 'draft'), + 'order_num' => (int) ($record->order_num ?? 0), + 'estimated_minutes' => $record->estimated_minutes, + 'published_at' => optional($record->published_at)?->format('Y-m-d\TH:i'), + 'seo_title' => (string) ($record->seo_title ?? ''), + 'seo_description' => (string) ($record->seo_description ?? ''), + 'meta_keywords' => (string) ($record->meta_keywords ?? ''), + 'og_title' => (string) ($record->og_title ?? ''), + 'og_description' => (string) ($record->og_description ?? ''), + 'og_image' => (string) ($record->og_image ?? ''), + 'is_featured' => (bool) ($record->is_featured ?? false), + ], 'categories' => [ 'type' => (string) ($record->type ?? 'lesson'), 'name' => (string) ($record->name ?? ''), @@ -679,15 +901,25 @@ final class AcademyAdminController extends Controller ], 'lessons' => [ 'category_id' => $record->category_id, + 'course_ids' => $record instanceof AcademyLesson + ? $record->courses()->pluck('academy_courses.id')->map(static fn ($id): string => (string) $id)->values()->all() + : [], 'title' => (string) ($record->title ?? ''), 'slug' => (string) ($record->slug ?? ''), + 'lesson_number' => $record->lesson_number, + 'course_order' => $record->course_order, + 'series_name' => (string) ($record->series_name ?? ''), 'excerpt' => (string) ($record->excerpt ?? ''), 'content' => (string) ($record->content ?? ''), + 'content_markdown' => (string) ($record->content_markdown ?? ''), 'difficulty' => (string) ($record->difficulty ?? 'beginner'), 'access_level' => (string) ($record->access_level ?? 'free'), 'lesson_type' => (string) ($record->lesson_type ?? 'article'), 'cover_image' => (string) ($record->cover_image ?? ''), 'cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($record->cover_image ?? '')), + 'article_cover_image' => (string) ($record->article_cover_image ?? ''), + 'article_cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($record->article_cover_image ?? '')), + 'tags' => implode(', ', (array) ($record->tags ?? [])), 'video_url' => (string) ($record->video_url ?? ''), 'reading_minutes' => (int) ($record->reading_minutes ?? 5), 'published_at' => optional($record->published_at)?->format('Y-m-d\TH:i'), @@ -712,6 +944,7 @@ final class AcademyAdminController extends Controller 'access_level' => (string) ($record->access_level ?? 'free'), 'aspect_ratio' => (string) ($record->aspect_ratio ?? ''), 'tags' => implode(', ', (array) ($record->tags ?? [])), + 'tool_notes' => $this->serializePromptToolNotes((array) ($record->tool_notes ?? [])), 'preview_image' => (string) ($record->preview_image ?? ''), 'preview_image_url' => $this->resolvePromptPreviewImageUrl((string) ($record->preview_image ?? '')), 'preview_image_file' => null, @@ -776,17 +1009,44 @@ final class AcademyAdminController extends Controller private function persistLessonAttributes(UpsertAcademyLessonRequest $request, ?AcademyLesson $lesson = null): array { $validated = $request->validated(); + unset($validated['course_ids']); unset($validated['blocks']); + $contentSource = in_array((string) ($validated['content_source'] ?? ''), ['html', 'markdown'], true) + ? (string) $validated['content_source'] + : ((string) ($lesson?->content_markdown ?? '') !== '' ? 'markdown' : 'html'); + $contentMarkdown = $this->nullableTrimmedString($validated['content_markdown'] ?? null); + $contentHtml = $this->nullableTrimmedString($validated['content'] ?? null); $currentCoverImage = trim((string) ($lesson?->cover_image ?? '')); + $currentArticleCoverImage = trim((string) ($lesson?->article_cover_image ?? '')); $nextCoverImage = filled($validated['cover_image'] ?? null) ? trim((string) $validated['cover_image']) : null; + $nextArticleCoverImage = filled($validated['article_cover_image'] ?? null) + ? trim((string) $validated['article_cover_image']) + : null; if ($currentCoverImage !== '' && $currentCoverImage !== (string) $nextCoverImage) { $this->deleteStoredLessonCoverIfLocal($currentCoverImage); } + if ($currentArticleCoverImage !== '' && $currentArticleCoverImage !== (string) $nextArticleCoverImage) { + $this->deleteStoredLessonCoverIfLocal($currentArticleCoverImage); + } + $validated['cover_image'] = $nextCoverImage; + $validated['article_cover_image'] = $nextArticleCoverImage; + $validated['content_markdown'] = $contentSource === 'markdown' ? $contentMarkdown : null; + $validated['content'] = $contentSource === 'markdown' ? $contentHtml : $contentHtml; + unset($validated['content_source']); + + if ($contentSource === 'markdown' && $contentMarkdown !== null) { + $validated['content'] = $this->lessonMarkdownRenderer->render($contentMarkdown); + } + + $validated['reading_minutes'] = $this->estimateLessonReadingMinutes( + (string) ($validated['content'] ?? ''), + (string) ($validated['content_markdown'] ?? ''), + ); // Auto-publish: if marked active but no published_at set, default to now. if (! empty($validated['active']) && empty($validated['published_at'])) { @@ -796,6 +1056,512 @@ final class AcademyAdminController extends Controller return $validated; } + /** + * @param array $courseIds + */ + private function syncLessonCourses(AcademyLesson $lesson, array $courseIds): void + { + $selectedCourseIds = collect($courseIds) + ->map(static fn ($courseId): int => (int) $courseId) + ->filter(static fn (int $courseId): bool => $courseId > 0) + ->unique() + ->values(); + + $existingCourseLessons = $lesson->courseLessons()->get()->keyBy(fn (AcademyCourseLesson $courseLesson): int => (int) $courseLesson->course_id); + $affectedCourseIds = $existingCourseLessons->keys()->map(static fn ($courseId): int => (int) $courseId) + ->merge($selectedCourseIds) + ->unique() + ->values(); + + foreach ($selectedCourseIds as $courseId) { + if ($existingCourseLessons->has($courseId)) { + continue; + } + + AcademyCourseLesson::query()->create([ + 'course_id' => $courseId, + 'lesson_id' => $lesson->id, + 'section_id' => null, + 'order_num' => (int) ((AcademyCourseLesson::query()->where('course_id', $courseId)->max('order_num') ?? -1) + 1), + 'is_required' => true, + 'access_override' => null, + 'unlock_after_lesson_id' => null, + ]); + } + + $existingCourseLessons + ->reject(fn (AcademyCourseLesson $courseLesson, int $courseId): bool => $selectedCourseIds->contains($courseId)) + ->each(fn (AcademyCourseLesson $courseLesson): bool|null => $courseLesson->delete()); + + if ($affectedCourseIds->isEmpty()) { + return; + } + + $affectedCourseIds->each(fn (int $courseId): bool => tap(true, fn () => $this->courseLessonOrdering->syncCourse($courseId))); + + $counts = AcademyCourseLesson::query() + ->selectRaw('course_id, count(*) as aggregate') + ->whereIn('course_id', $affectedCourseIds->all()) + ->groupBy('course_id') + ->pluck('aggregate', 'course_id'); + + AcademyCourse::query() + ->whereIn('id', $affectedCourseIds->all()) + ->get() + ->each(function (AcademyCourse $course) use ($counts): void { + $course->forceFill([ + 'lessons_count_cache' => (int) ($counts[$course->id] ?? 0), + ])->save(); + }); + } + + /** + * @return array> + */ + private function courseOptions(): array + { + return AcademyCourse::query() + ->orderByDesc('is_featured') + ->orderBy('order_num') + ->orderBy('title') + ->get() + ->map(fn (AcademyCourse $course): array => [ + 'value' => (string) $course->id, + 'label' => (string) $course->title, + ]) + ->values() + ->all(); + } + + /** + * @return array>> + */ + private function serializeLessonNumberingContext(?AcademyLesson $currentLesson): array + { + $otherLessons = AcademyLesson::query() + ->when($currentLesson?->exists, fn ($query) => $query->whereKeyNot($currentLesson->id)) + ->get(['lesson_number', 'course_order']); + + return [ + 'lesson_number' => $this->buildSequentialNumberSummary($otherLessons->pluck('lesson_number')->all()), + 'course_order' => $this->buildSequentialNumberSummary($otherLessons->pluck('course_order')->all()), + ]; + } + + /** + * @return array> + */ + private function serializeLessonEditorCourses(?AcademyLesson $currentLesson): array + { + return AcademyCourse::query() + ->with([ + 'courseLessons' => fn ($query) => $query + ->with(['lesson:id,title,slug,lesson_number,series_name', 'section:id,title']) + ->orderBy('order_num') + ->orderBy('id'), + ]) + ->orderByDesc('is_featured') + ->orderBy('order_num') + ->orderBy('title') + ->get() + ->map(function (AcademyCourse $course) use ($currentLesson): array { + $courseLessons = $course->courseLessons + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values(); + + return [ + 'value' => (string) $course->id, + 'label' => (string) $course->title, + 'description' => sprintf('%s · %s', (string) $course->difficulty, (string) $course->status), + 'status' => (string) $course->status, + 'difficulty' => (string) $course->difficulty, + 'access_level' => (string) $course->access_level, + 'lesson_count' => $courseLessons->count(), + 'next_order_num' => (int) (($courseLessons->max('order_num') ?? -1) + 1), + 'attach_url' => route('admin.academy.courses.lessons.attach', ['academyCourse' => $course]), + 'reorder_url' => route('admin.academy.courses.reorder', ['academyCourse' => $course]), + 'builder_url' => route('admin.academy.courses.builder.edit', ['academyCourse' => $course]), + 'edit_url' => route('admin.academy.courses.edit', ['academyCourse' => $course]), + 'preview_url' => route('academy.courses.show', ['course' => $course->slug]), + 'lessons' => $courseLessons->map(function (AcademyCourseLesson $courseLesson) use ($currentLesson, $course): array { + $lesson = $courseLesson->lesson; + + return [ + 'id' => (int) $courseLesson->id, + 'lesson_id' => (int) $courseLesson->lesson_id, + 'title' => (string) ($lesson?->title ?? 'Untitled lesson'), + 'slug' => (string) ($lesson?->slug ?? ''), + 'section_id' => $courseLesson->section_id ? (int) $courseLesson->section_id : null, + 'order_num' => (int) ($courseLesson->order_num ?? 0), + 'display_order' => (int) ($courseLesson->order_num ?? 0) + 1, + 'formatted_lesson_number' => $lesson?->formatted_lesson_number, + 'series_name' => (string) ($lesson?->series_name ?? ''), + 'section_title' => (string) ($courseLesson->section?->title ?? ''), + 'is_required' => (bool) $courseLesson->is_required, + 'is_current' => $currentLesson?->exists + ? (int) $courseLesson->lesson_id === (int) $currentLesson->id + : false, + 'destroy_url' => route('admin.academy.courses.lessons.destroy', ['academyCourse' => $course, 'academyCourseLesson' => $courseLesson]), + 'edit_url' => $lesson instanceof AcademyLesson + ? route('admin.academy.lessons.edit', ['academyLesson' => $lesson]) + : null, + ]; + })->all(), + ]; + }) + ->values() + ->all(); + } + + /** + * @return array> + */ + private function serializeLessonRevisions(AcademyLesson $lesson): array + { + return AcademyLessonRevision::query() + ->with('user') + ->where('lesson_id', $lesson->id) + ->latest('id') + ->limit(12) + ->get() + ->map(function (AcademyLessonRevision $revision) use ($lesson): array { + $snapshot = is_array($revision->snapshot_json) ? $revision->snapshot_json : []; + $fields = is_array($snapshot['fields'] ?? null) ? $snapshot['fields'] : []; + + return [ + 'id' => (int) $revision->id, + 'created_at' => $revision->created_at?->toISOString(), + 'created_label' => $revision->created_at?->diffForHumans(), + 'actor_name' => (string) ($revision->user?->name ?? 'Staff'), + 'change_note' => (string) ($revision->change_note ?? ''), + 'restore_url' => route('admin.academy.lessons.revisions.restore', [ + 'academyLesson' => $lesson, + 'academyLessonRevision' => $revision, + ]), + 'snapshot' => [ + 'title' => (string) ($fields['title'] ?? ''), + 'excerpt' => (string) ($fields['excerpt'] ?? ''), + 'content_preview' => Str::limit(strip_tags((string) ($fields['content'] ?? '')), 180), + 'course_count' => count((array) ($snapshot['course_ids'] ?? [])), + 'block_count' => count((array) ($snapshot['blocks'] ?? [])), + ], + ]; + }) + ->values() + ->all(); + } + + /** + * @param array $values + * @return array> + */ + private function buildSequentialNumberSummary(array $values): array + { + $numbers = collect($values) + ->map(static fn ($value): int => (int) $value) + ->filter(static fn (int $value): bool => $value > 0) + ->unique() + ->sort() + ->values(); + + $missing = []; + $expected = 1; + + foreach ($numbers as $number) { + while ($expected < $number) { + $missing[] = $expected; + $expected += 1; + } + + $expected = $number + 1; + } + + return [ + 'suggested' => (int) ($missing[0] ?? (($numbers->last() ?? 0) + 1)), + 'highest' => (int) ($numbers->last() ?? 0), + 'used_count' => $numbers->count(), + 'missing' => array_slice($missing, 0, 8), + ]; + } + + private function createLessonRevision(AcademyLesson $lesson, ?User $user, ?string $changeNote = null): AcademyLessonRevision + { + $lesson->loadMissing(['blocks.comparisonResults', 'courses']); + + return AcademyLessonRevision::query()->create([ + 'lesson_id' => $lesson->id, + 'user_id' => $user?->id, + 'change_note' => $this->nullableTrimmedString($changeNote), + 'snapshot_json' => $this->serializeLessonRevisionSnapshot($lesson), + ]); + } + + /** + * @return array + */ + private function serializeLessonRevisionSnapshot(AcademyLesson $lesson): array + { + return [ + 'content_source' => (string) ($lesson->content_markdown ? 'markdown' : 'html'), + 'fields' => [ + 'category_id' => $lesson->category_id, + 'title' => (string) $lesson->title, + 'slug' => (string) $lesson->slug, + 'lesson_number' => $lesson->lesson_number, + 'course_order' => $lesson->course_order, + 'series_name' => (string) ($lesson->series_name ?? ''), + 'excerpt' => (string) ($lesson->excerpt ?? ''), + 'content' => (string) ($lesson->content ?? ''), + 'content_markdown' => (string) ($lesson->content_markdown ?? ''), + 'difficulty' => (string) $lesson->difficulty, + 'access_level' => (string) $lesson->access_level, + 'lesson_type' => (string) $lesson->lesson_type, + 'cover_image' => (string) ($lesson->cover_image ?? ''), + 'article_cover_image' => (string) ($lesson->article_cover_image ?? ''), + 'tags' => array_values((array) ($lesson->tags ?? [])), + 'video_url' => (string) ($lesson->video_url ?? ''), + 'reading_minutes' => (int) ($lesson->reading_minutes ?? 5), + 'featured' => (bool) ($lesson->featured ?? false), + 'active' => (bool) ($lesson->active ?? true), + 'published_at' => $lesson->published_at?->toISOString(), + 'seo_title' => (string) ($lesson->seo_title ?? ''), + 'seo_description' => (string) ($lesson->seo_description ?? ''), + ], + 'course_ids' => $lesson->courses->pluck('id')->map(static fn ($id): int => (int) $id)->values()->all(), + 'blocks' => $lesson->blocks->map(fn (AcademyLessonBlock $block): array => $this->serializeLessonBlock($block))->values()->all(), + ]; + } + + private function applyLessonRevisionSnapshot(AcademyLesson $lesson, AcademyLessonRevision $revision, ?string $field = null): void + { + $snapshot = is_array($revision->snapshot_json) ? $revision->snapshot_json : []; + $fields = is_array($snapshot['fields'] ?? null) ? $snapshot['fields'] : []; + + if ($field === 'course_ids') { + $this->syncLessonCourses($lesson, array_values((array) ($snapshot['course_ids'] ?? []))); + + return; + } + + if ($field === 'blocks') { + $this->syncLessonBlocks($lesson, array_values((array) ($snapshot['blocks'] ?? []))); + + return; + } + + if ($field === 'content') { + $contentSource = (string) ($snapshot['content_source'] ?? 'html'); + $lesson->forceFill([ + 'content' => (string) ($fields['content'] ?? ''), + 'content_markdown' => $contentSource === 'markdown' + ? $this->nullableTrimmedString($fields['content_markdown'] ?? null) + : null, + ])->save(); + + return; + } + + if (is_string($field) && $field !== '') { + $lesson->forceFill([$field => $this->normalizeLessonRevisionFieldValue($field, $fields[$field] ?? null)])->save(); + + return; + } + + $lesson->forceFill([ + 'category_id' => $fields['category_id'] ?? null, + 'title' => (string) ($fields['title'] ?? ''), + 'slug' => (string) ($fields['slug'] ?? ''), + 'lesson_number' => $fields['lesson_number'] ?? null, + 'course_order' => $fields['course_order'] ?? null, + 'series_name' => (string) ($fields['series_name'] ?? ''), + 'excerpt' => (string) ($fields['excerpt'] ?? ''), + 'content' => (string) ($fields['content'] ?? ''), + 'content_markdown' => (string) ($snapshot['content_source'] ?? 'html') === 'markdown' + ? $this->nullableTrimmedString($fields['content_markdown'] ?? null) + : null, + 'difficulty' => (string) ($fields['difficulty'] ?? 'beginner'), + 'access_level' => (string) ($fields['access_level'] ?? 'free'), + 'lesson_type' => (string) ($fields['lesson_type'] ?? 'article'), + 'cover_image' => $this->nullableTrimmedString($fields['cover_image'] ?? null), + 'article_cover_image' => $this->nullableTrimmedString($fields['article_cover_image'] ?? null), + 'tags' => array_values((array) ($fields['tags'] ?? [])), + 'video_url' => $this->nullableTrimmedString($fields['video_url'] ?? null), + 'reading_minutes' => (int) ($fields['reading_minutes'] ?? 5), + 'featured' => (bool) ($fields['featured'] ?? false), + 'active' => (bool) ($fields['active'] ?? true), + 'published_at' => filled($fields['published_at'] ?? null) ? Carbon::parse((string) $fields['published_at']) : null, + 'seo_title' => $this->nullableTrimmedString($fields['seo_title'] ?? null), + 'seo_description' => $this->nullableTrimmedString($fields['seo_description'] ?? null), + ])->save(); + + $this->syncLessonCourses($lesson, array_values((array) ($snapshot['course_ids'] ?? []))); + $this->syncLessonBlocks($lesson, array_values((array) ($snapshot['blocks'] ?? []))); + } + + private function normalizeLessonRevisionFieldValue(string $field, mixed $value): mixed + { + return match ($field) { + 'category_id', 'lesson_number', 'course_order' => filled($value) ? (int) $value : null, + 'reading_minutes' => max(1, (int) ($value ?? 5)), + 'featured', 'active' => (bool) $value, + 'published_at' => filled($value) ? Carbon::parse((string) $value) : null, + 'cover_image', 'article_cover_image', 'video_url', 'seo_title', 'seo_description' => $this->nullableTrimmedString($value), + 'tags' => array_values((array) ($value ?? [])), + default => is_string($value) ? $value : $value, + }; + } + + private function estimateLessonReadingMinutes(?string $contentHtml, ?string $contentMarkdown): int + { + $source = trim((string) ($contentMarkdown ?: '')); + + if ($source === '') { + $source = html_entity_decode(strip_tags((string) ($contentHtml ?? '')), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + $normalized = preg_replace('/\s+/u', ' ', trim((string) $source)); + + if (! is_string($normalized) || $normalized === '') { + return 1; + } + + $words = preg_split('/\s+/u', $normalized, -1, PREG_SPLIT_NO_EMPTY); + $wordCount = is_array($words) ? count($words) : 0; + + return max(1, (int) ceil($wordCount / 180)); + } + + private function persistCourseAttributes(UpsertAcademyCourseRequest $request, ?AcademyCourse $course = null): array + { + $validated = $request->validated(); + $currentCoverImage = trim((string) ($course?->cover_image ?? '')); + $currentTeaserImage = trim((string) ($course?->teaser_image ?? '')); + $nextCoverImage = $this->nullableTrimmedString($validated['cover_image'] ?? null); + $nextTeaserImage = $this->nullableTrimmedString($validated['teaser_image'] ?? null); + + if ($currentCoverImage !== '' && $currentCoverImage !== (string) $nextCoverImage) { + $this->deleteStoredLessonCoverIfLocal($currentCoverImage); + } + + if ($currentTeaserImage !== '' && $currentTeaserImage !== (string) $nextTeaserImage) { + $this->deleteStoredLessonCoverIfLocal($currentTeaserImage); + } + + $validated['cover_image'] = $nextCoverImage; + $validated['teaser_image'] = $nextTeaserImage; + $validated['seo_title'] = $this->nullableTrimmedString($validated['seo_title'] ?? null); + $validated['seo_description'] = $this->nullableTrimmedString($validated['seo_description'] ?? null); + $validated['meta_keywords'] = $this->nullableTrimmedString($validated['meta_keywords'] ?? null); + $validated['og_title'] = $this->nullableTrimmedString($validated['og_title'] ?? null); + $validated['og_description'] = $this->nullableTrimmedString($validated['og_description'] ?? null); + $validated['og_image'] = $this->nullableTrimmedString($validated['og_image'] ?? null); + + if ((string) ($validated['status'] ?? '') === 'published' && empty($validated['published_at'])) { + $validated['published_at'] = now(); + } + + return $validated; + } + + /** + * @return array + */ + /** + * @return array> + */ + private function serializeCourseEditorLessons(AcademyCourse $course): array + { + $course->loadMissing(['courseLessons.lesson', 'courseLessons.section']); + + return $course->courseLessons + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values() + ->map(function (AcademyCourseLesson $courseLesson) use ($course): array { + $lesson = $courseLesson->lesson; + + return [ + 'id' => (int) $courseLesson->id, + 'lesson_id' => (int) $courseLesson->lesson_id, + 'title' => (string) ($lesson?->title ?? 'Untitled lesson'), + 'slug' => (string) ($lesson?->slug ?? ''), + 'section_id' => $courseLesson->section_id ? (int) $courseLesson->section_id : null, + 'section_title' => (string) ($courseLesson->section?->title ?? ''), + 'order_num' => (int) ($courseLesson->order_num ?? 0), + 'display_order' => (int) ($courseLesson->order_num ?? 0) + 1, + 'formatted_lesson_number' => $lesson instanceof AcademyLesson ? $lesson->formatted_lesson_number : null, + 'is_required' => (bool) $courseLesson->is_required, + 'difficulty' => (string) ($lesson?->difficulty ?? ''), + 'access_level' => (string) ($lesson?->access_level ?? ''), + 'destroy_url' => route('admin.academy.courses.lessons.destroy', [ + 'academyCourse' => $course, + 'academyCourseLesson' => $courseLesson, + ]), + 'edit_url' => $lesson instanceof AcademyLesson + ? route('admin.academy.lessons.edit', ['academyLesson' => $lesson]) + : null, + ]; + }) + ->all(); + } + + /** + * @return array> + */ + private function serializeCourseAvailableLessons(AcademyCourse $course): array + { + $course->loadMissing(['courseLessons']); + + $attachedLessonIds = $course->courseLessons + ->pluck('lesson_id') + ->map(fn ($id): int => (int) $id) + ->flip() + ->all(); + + return AcademyLesson::query() + ->with('category') + ->orderBy('title') + ->get() + ->map(fn (AcademyLesson $lesson): array => [ + 'id' => (int) $lesson->id, + 'title' => (string) $lesson->title, + 'slug' => (string) $lesson->slug, + 'difficulty' => (string) $lesson->difficulty, + 'access_level' => (string) $lesson->access_level, + 'active' => (bool) $lesson->active, + 'category' => $lesson->category ? (string) $lesson->category->name : '', + 'attached' => isset($attachedLessonIds[(int) $lesson->id]), + ]) + ->values() + ->all(); + } + + private function serializeCourseOutlineSummary(AcademyCourse $course): array + { + $course->loadMissing(['sections', 'courseLessons']); + + $sections = $course->sections + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values(); + + $courseLessons = $course->courseLessons; + + return [ + 'section_count' => $sections->count(), + 'visible_section_count' => $sections->where('is_visible', true)->count(), + 'lesson_count' => $courseLessons->count(), + 'required_lesson_count' => $courseLessons->where('is_required', true)->count(), + 'unsectioned_lesson_count' => $courseLessons->whereNull('section_id')->count(), + 'sections' => $sections->map(function ($section) use ($courseLessons): array { + return [ + 'id' => (int) $section->id, + 'title' => (string) $section->title, + 'is_visible' => (bool) $section->is_visible, + 'lesson_count' => $courseLessons->where('section_id', $section->id)->count(), + ]; + })->all(), + ]; + } + /** * @param array> $blocks */ @@ -960,6 +1726,15 @@ final class AcademyAdminController extends Controller { $validated = $request->validated(); unset($validated['preview_image_file']); + $newCategoryName = trim((string) ($validated['new_category_name'] ?? '')); + unset($validated['new_category_name']); + + if ($newCategoryName !== '') { + $validated['category_id'] = $this->resolveOrCreatePromptCategoryId($newCategoryName); + } + + $validated['tool_notes'] = $this->normalizePromptToolNotes((array) ($validated['tool_notes'] ?? [])); + $previousToolNotes = $this->normalizePromptToolNotes((array) ($prompt?->tool_notes ?? [])); $currentPreviewImage = (string) ($prompt?->preview_image ?? ''); $previewImageFile = $this->promptPreviewImageUpload($request); @@ -978,9 +1753,146 @@ final class AcademyAdminController extends Controller $validated['published_at'] = now(); } + $this->deleteRemovedPromptComparisonMedia($previousToolNotes, (array) ($validated['tool_notes'] ?? [])); + return $validated; } + private function resolveOrCreatePromptCategoryId(string $name): int + { + $normalizedName = trim($name); + + $existing = AcademyCategory::query() + ->where('type', 'prompt') + ->whereRaw('LOWER(name) = ?', [mb_strtolower($normalizedName)]) + ->first(); + + if ($existing instanceof AcademyCategory) { + return (int) $existing->id; + } + + $maxOrder = (int) (AcademyCategory::query()->where('type', 'prompt')->max('order_num') ?? 0); + + $category = new AcademyCategory(); + $category->forceFill([ + 'type' => 'prompt', + 'name' => $normalizedName, + 'slug' => Str::slug($normalizedName), + 'description' => null, + 'order_num' => $maxOrder + 1, + 'active' => true, + ])->save(); + + return (int) $category->id; + } + + /** + * @param array $notes + * @return array> + */ + private function serializePromptToolNotes(array $notes): array + { + return collect($this->normalizePromptToolNotes($notes)) + ->map(fn (array $note): array => [ + ...$note, + 'image_url' => $this->resolveLessonMediaUrl($note['image_path'] ?? null), + 'thumb_url' => $this->resolveLessonMediaUrl($note['thumb_path'] ?? null), + ]) + ->values() + ->all(); + } + + /** + * @param array $notes + * @return array> + */ + private function normalizePromptToolNotes(array $notes): array + { + return collect($notes) + ->map(function ($note): ?array { + if (is_string($note)) { + $normalized = [ + 'provider' => '', + 'model_name' => '', + 'notes' => trim($note), + 'strengths' => '', + 'weaknesses' => '', + 'best_for' => '', + 'image_path' => '', + 'thumb_path' => '', + 'settings' => '', + 'score' => null, + 'active' => true, + ]; + + return $normalized['notes'] !== '' ? $normalized : null; + } + + if (! is_array($note)) { + return null; + } + + $normalized = [ + 'provider' => trim((string) ($note['provider'] ?? '')), + 'model_name' => trim((string) ($note['model_name'] ?? '')), + 'notes' => trim((string) ($note['notes'] ?? '')), + 'strengths' => trim((string) ($note['strengths'] ?? '')), + 'weaknesses' => trim((string) ($note['weaknesses'] ?? '')), + 'best_for' => trim((string) ($note['best_for'] ?? '')), + 'image_path' => trim((string) ($note['image_path'] ?? '')), + 'thumb_path' => trim((string) ($note['thumb_path'] ?? '')), + 'settings' => trim((string) ($note['settings'] ?? '')), + 'score' => filled($note['score'] ?? null) ? max(1, min(10, (int) $note['score'])) : null, + 'active' => filter_var($note['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true, + ]; + + $hasContent = collect([ + $normalized['provider'], + $normalized['model_name'], + $normalized['notes'], + $normalized['strengths'], + $normalized['weaknesses'], + $normalized['best_for'], + $normalized['image_path'], + $normalized['thumb_path'], + $normalized['settings'], + ])->contains(fn (string $value): bool => $value !== ''); + + return $hasContent || $normalized['score'] !== null ? $normalized : null; + }) + ->filter() + ->values() + ->all(); + } + + /** + * @param array> $previousNotes + * @param array> $nextNotes + */ + private function deleteRemovedPromptComparisonMedia(array $previousNotes, array $nextNotes): void + { + $previousPaths = collect($previousNotes) + ->flatMap(fn (array $note): array => [ + trim((string) ($note['image_path'] ?? '')), + trim((string) ($note['thumb_path'] ?? '')), + ]) + ->filter() + ->unique(); + + $nextPaths = collect($nextNotes) + ->flatMap(fn (array $note): array => [ + trim((string) ($note['image_path'] ?? '')), + trim((string) ($note['thumb_path'] ?? '')), + ]) + ->filter() + ->unique() + ->all(); + + $previousPaths + ->reject(fn (string $path): bool => in_array($path, $nextPaths, true)) + ->each(fn (string $path): bool => $this->deleteStoredLessonMediaIfLocal($path)); + } + private function promptPreviewImageUpload(UpsertAcademyPromptTemplateRequest $request): ?UploadedFile { $file = $request->file('preview_image_file'); @@ -1196,6 +2108,7 @@ final class AcademyAdminController extends Controller private function routeParams(string $resource, Model $record): array { return match ($resource) { + 'courses' => ['academyCourse' => $record], 'categories' => ['academyCategory' => $record], 'lessons' => ['academyLesson' => $record], 'prompts' => ['academyPromptTemplate' => $record], @@ -1237,6 +2150,14 @@ final class AcademyAdminController extends Controller ->all(); } + private function courseDifficultyOptions(): array + { + return collect(['beginner', 'intermediate', 'advanced']) + ->map(fn (string $value): array => ['value' => $value, 'label' => ucfirst($value)]) + ->values() + ->all(); + } + private function accessOptions(): array { return [ @@ -1246,6 +2167,25 @@ final class AcademyAdminController extends Controller ]; } + private function courseAccessOptions(): array + { + return [ + ['value' => 'free', 'label' => 'Free'], + ['value' => 'premium', 'label' => 'Premium'], + ['value' => 'mixed', 'label' => 'Mixed'], + ]; + } + + private function courseStatusOptions(): array + { + return [ + ['value' => 'draft', 'label' => 'Draft'], + ['value' => 'review', 'label' => 'Review'], + ['value' => 'published', 'label' => 'Published'], + ['value' => 'archived', 'label' => 'Archived'], + ]; + } + private function syncPackItems(AcademyPromptPack $pack, array $promptIds): void { AcademyPromptPackItem::query()->where('pack_id', $pack->id)->delete(); diff --git a/app/Http/Controllers/Settings/AcademyCourseBuilderController.php b/app/Http/Controllers/Settings/AcademyCourseBuilderController.php new file mode 100644 index 00000000..eb425f67 --- /dev/null +++ b/app/Http/Controllers/Settings/AcademyCourseBuilderController.php @@ -0,0 +1,308 @@ +load(['sections', 'courseLessons.section', 'courseLessons.lesson.category']); + + return Inertia::render('Admin/Academy/CourseBuilder', [ + 'course' => $this->serializeCourse($academyCourse), + 'sections' => $academyCourse->sections + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values() + ->map(fn (AcademyCourseSection $section): array => $this->serializeSection($section)) + ->all(), + 'courseLessons' => $academyCourse->courseLessons + ->sortBy([['order_num', 'asc'], ['id', 'asc']]) + ->values() + ->map(fn (AcademyCourseLesson $courseLesson): array => $this->serializeCourseLesson($courseLesson)) + ->all(), + 'availableLessons' => AcademyLesson::query() + ->with('category') + ->orderBy('title') + ->get() + ->map(fn (AcademyLesson $lesson): array => [ + 'id' => (int) $lesson->id, + 'title' => (string) $lesson->title, + 'slug' => (string) $lesson->slug, + 'excerpt' => (string) ($lesson->excerpt ?? ''), + 'difficulty' => (string) $lesson->difficulty, + 'access_level' => (string) $lesson->access_level, + 'active' => (bool) $lesson->active, + 'published_at' => $lesson->published_at?->toISOString(), + 'category' => $lesson->category ? (string) $lesson->category->name : '', + 'attached' => $academyCourse->courseLessons->contains(fn (AcademyCourseLesson $courseLesson): bool => (int) $courseLesson->lesson_id === (int) $lesson->id), + ]) + ->values() + ->all(), + 'routes' => [ + 'index' => route('admin.academy.courses.index'), + 'edit' => route('admin.academy.courses.edit', ['academyCourse' => $academyCourse]), + 'preview' => route('academy.courses.show', ['course' => $academyCourse->slug]), + 'sectionStore' => route('admin.academy.courses.sections.store', ['academyCourse' => $academyCourse]), + 'attachLesson' => route('admin.academy.courses.lessons.attach', ['academyCourse' => $academyCourse]), + 'reorder' => route('admin.academy.courses.reorder', ['academyCourse' => $academyCourse]), + ], + ]); + } + + public function storeSection(Request $request, AcademyCourse $academyCourse): RedirectResponse + { + $data = $request->validate([ + 'title' => ['required', 'string', 'max:180'], + 'slug' => ['nullable', 'string', 'max:180'], + 'description' => ['nullable', 'string'], + 'order_num' => ['nullable', 'integer', 'min:0'], + 'is_visible' => ['nullable', 'boolean'], + ]); + + $academyCourse->sections()->create([ + 'title' => $data['title'], + 'slug' => filled($data['slug'] ?? null) ? $data['slug'] : Str::slug($data['title']), + 'description' => $data['description'] ?? null, + 'order_num' => (int) ($data['order_num'] ?? ($academyCourse->sections()->max('order_num') + 1)), + 'is_visible' => (bool) ($data['is_visible'] ?? true), + ]); + + $this->cache->clearAll(); + + return back()->with('success', 'Course section created.'); + } + + public function updateSection(Request $request, AcademyCourse $academyCourse, AcademyCourseSection $academyCourseSection): RedirectResponse + { + abort_unless((int) $academyCourseSection->course_id === (int) $academyCourse->id, 404); + + $data = $request->validate([ + 'title' => ['required', 'string', 'max:180'], + 'slug' => ['nullable', 'string', 'max:180'], + 'description' => ['nullable', 'string'], + 'order_num' => ['nullable', 'integer', 'min:0'], + 'is_visible' => ['nullable', 'boolean'], + ]); + + $academyCourseSection->forceFill([ + 'title' => $data['title'], + 'slug' => filled($data['slug'] ?? null) ? $data['slug'] : Str::slug($data['title']), + 'description' => $data['description'] ?? null, + 'order_num' => (int) ($data['order_num'] ?? 0), + 'is_visible' => (bool) ($data['is_visible'] ?? true), + ])->save(); + + $this->cache->clearAll(); + + return back()->with('success', 'Course section updated.'); + } + + public function destroySection(AcademyCourse $academyCourse, AcademyCourseSection $academyCourseSection): RedirectResponse + { + abort_unless((int) $academyCourseSection->course_id === (int) $academyCourse->id, 404); + + $academyCourseSection->delete(); + $this->cache->clearAll(); + + return back()->with('success', 'Course section deleted.'); + } + + public function attachLesson(Request $request, AcademyCourse $academyCourse): RedirectResponse + { + $data = $request->validate([ + 'lesson_id' => ['required', 'integer', 'exists:academy_lessons,id'], + 'section_id' => ['nullable', 'integer', 'exists:academy_course_sections,id'], + 'order_num' => ['nullable', 'integer', 'min:0'], + 'is_required' => ['nullable', 'boolean'], + 'access_override' => ['nullable', 'string', 'in:free,premium,creator,pro'], + 'unlock_after_lesson_id' => ['nullable', 'integer', 'exists:academy_lessons,id'], + ]); + + if (AcademyCourseLesson::query()->where('course_id', $academyCourse->id)->where('lesson_id', $data['lesson_id'])->exists()) { + return back()->with('error', 'That lesson is already attached to this course.'); + } + + $sectionId = $data['section_id'] ?? null; + if ($sectionId !== null) { + abort_unless(AcademyCourseSection::query()->where('course_id', $academyCourse->id)->whereKey($sectionId)->exists(), 404); + } + + AcademyCourseLesson::query()->create([ + 'course_id' => $academyCourse->id, + 'section_id' => $sectionId, + 'lesson_id' => (int) $data['lesson_id'], + 'order_num' => (int) ($data['order_num'] ?? ($academyCourse->courseLessons()->max('order_num') + 1)), + 'is_required' => (bool) ($data['is_required'] ?? true), + 'access_override' => $data['access_override'] ?? null, + 'unlock_after_lesson_id' => $data['unlock_after_lesson_id'] ?? null, + ]); + + $this->courseLessonOrdering->syncCourse($academyCourse); + $this->syncCourseCounts($academyCourse); + + return back()->with('success', 'Lesson attached to course.'); + } + + public function updateCourseLesson(Request $request, AcademyCourse $academyCourse, AcademyCourseLesson $academyCourseLesson): RedirectResponse + { + abort_unless((int) $academyCourseLesson->course_id === (int) $academyCourse->id, 404); + + $data = $request->validate([ + 'section_id' => ['nullable', 'integer', 'exists:academy_course_sections,id'], + 'order_num' => ['nullable', 'integer', 'min:0'], + 'is_required' => ['nullable', 'boolean'], + 'access_override' => ['nullable', 'string', 'in:free,premium,creator,pro'], + 'unlock_after_lesson_id' => ['nullable', 'integer', 'exists:academy_lessons,id'], + ]); + + $sectionId = $data['section_id'] ?? null; + if ($sectionId !== null) { + abort_unless(AcademyCourseSection::query()->where('course_id', $academyCourse->id)->whereKey($sectionId)->exists(), 404); + } + + $academyCourseLesson->forceFill([ + 'section_id' => $sectionId, + 'order_num' => (int) ($data['order_num'] ?? 0), + 'is_required' => (bool) ($data['is_required'] ?? true), + 'access_override' => $data['access_override'] ?? null, + 'unlock_after_lesson_id' => $data['unlock_after_lesson_id'] ?? null, + ])->save(); + + $this->courseLessonOrdering->syncCourse($academyCourse); + $this->syncCourseCounts($academyCourse); + + return back()->with('success', 'Course lesson updated.'); + } + + public function detachLesson(AcademyCourse $academyCourse, AcademyCourseLesson $academyCourseLesson): RedirectResponse + { + abort_unless((int) $academyCourseLesson->course_id === (int) $academyCourse->id, 404); + + $academyCourseLesson->delete(); + $this->courseLessonOrdering->syncCourse($academyCourse); + $this->syncCourseCounts($academyCourse); + + return back()->with('success', 'Lesson removed from course.'); + } + + public function reorder(Request $request, AcademyCourse $academyCourse): RedirectResponse + { + $data = $request->validate([ + 'sections' => ['nullable', 'array'], + 'sections.*.id' => ['required', 'integer', 'exists:academy_course_sections,id'], + 'sections.*.order_num' => ['required', 'integer', 'min:0'], + 'lessons' => ['nullable', 'array'], + 'lessons.*.id' => ['required', 'integer', 'exists:academy_course_lessons,id'], + 'lessons.*.order_num' => ['required', 'integer', 'min:0'], + 'lessons.*.section_id' => ['nullable', 'integer', 'exists:academy_course_sections,id'], + ]); + + foreach ((array) ($data['sections'] ?? []) as $sectionData) { + AcademyCourseSection::query() + ->where('course_id', $academyCourse->id) + ->whereKey((int) $sectionData['id']) + ->update(['order_num' => (int) $sectionData['order_num']]); + } + + foreach ((array) ($data['lessons'] ?? []) as $lessonData) { + AcademyCourseLesson::query() + ->where('course_id', $academyCourse->id) + ->whereKey((int) $lessonData['id']) + ->update([ + 'order_num' => (int) $lessonData['order_num'], + 'section_id' => $lessonData['section_id'] ?? null, + ]); + } + + $this->courseLessonOrdering->syncCourse($academyCourse); + $this->syncCourseCounts($academyCourse); + + return back()->with('success', 'Course order updated.'); + } + + private function syncCourseCounts(AcademyCourse $academyCourse): void + { + $academyCourse->forceFill([ + 'lessons_count_cache' => $academyCourse->courseLessons()->count(), + ])->save(); + + $this->cache->clearAll(); + } + + private function serializeCourse(AcademyCourse $course): array + { + return [ + 'id' => (int) $course->id, + 'title' => (string) $course->title, + 'slug' => (string) $course->slug, + 'subtitle' => (string) ($course->subtitle ?? ''), + 'excerpt' => (string) ($course->excerpt ?? ''), + 'description' => (string) ($course->description ?? ''), + 'access_level' => (string) $course->access_level, + 'difficulty' => (string) $course->difficulty, + 'status' => (string) $course->status, + 'lessons_count_cache' => (int) ($course->lessons_count_cache ?? 0), + 'cover_image' => (string) ($course->cover_image ?? ''), + 'published_at' => $course->published_at?->toISOString(), + ]; + } + + private function serializeSection(AcademyCourseSection $section): array + { + return [ + 'id' => (int) $section->id, + 'title' => (string) $section->title, + 'slug' => (string) ($section->slug ?? ''), + 'description' => (string) ($section->description ?? ''), + 'order_num' => (int) ($section->order_num ?? 0), + 'is_visible' => (bool) ($section->is_visible ?? true), + 'updateUrl' => route('admin.academy.courses.sections.update', ['academyCourse' => $section->course_id, 'academyCourseSection' => $section]), + 'destroyUrl' => route('admin.academy.courses.sections.destroy', ['academyCourse' => $section->course_id, 'academyCourseSection' => $section]), + ]; + } + + private function serializeCourseLesson(AcademyCourseLesson $courseLesson): array + { + $lesson = $courseLesson->lesson; + + return [ + 'id' => (int) $courseLesson->id, + 'section_id' => $courseLesson->section_id ? (int) $courseLesson->section_id : null, + 'lesson_id' => (int) $courseLesson->lesson_id, + 'title' => (string) ($lesson?->title ?? ''), + 'slug' => (string) ($lesson?->slug ?? ''), + 'excerpt' => (string) ($lesson?->excerpt ?? ''), + 'difficulty' => (string) ($lesson?->difficulty ?? ''), + 'access_level' => (string) ($lesson?->access_level ?? ''), + 'category' => (string) ($lesson?->category?->name ?? ''), + 'order_num' => (int) ($courseLesson->order_num ?? 0), + 'is_required' => (bool) $courseLesson->is_required, + 'access_override' => $courseLesson->access_override, + 'unlock_after_lesson_id' => $courseLesson->unlock_after_lesson_id ? (int) $courseLesson->unlock_after_lesson_id : null, + 'updateUrl' => route('admin.academy.courses.lessons.update', ['academyCourse' => $courseLesson->course_id, 'academyCourseLesson' => $courseLesson]), + 'destroyUrl' => route('admin.academy.courses.lessons.destroy', ['academyCourse' => $courseLesson->course_id, 'academyCourseLesson' => $courseLesson]), + ]; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Settings/CollectionInsightsController.php b/app/Http/Controllers/Settings/CollectionInsightsController.php index 88ec54ff..24e35d98 100644 --- a/app/Http/Controllers/Settings/CollectionInsightsController.php +++ b/app/Http/Controllers/Settings/CollectionInsightsController.php @@ -111,7 +111,7 @@ class CollectionInsightsController extends Controller 'title' => 'Collections Dashboard — Skinbase', 'description' => 'Overview of collection lifecycle, quality, activity, and upcoming collection campaigns.', 'canonical' => route('settings.collections.dashboard'), - 'robots' => 'noindex,follow', + 'robots' => 'index,follow', ], ])->rootView('collections'); } @@ -130,7 +130,7 @@ class CollectionInsightsController extends Controller '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', + 'robots' => 'index,follow', ], ])->rootView('collections'); } @@ -153,7 +153,7 @@ class CollectionInsightsController extends Controller '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', + 'robots' => 'index,follow', ], ])->rootView('collections'); } diff --git a/app/Http/Controllers/Settings/CollectionProgrammingController.php b/app/Http/Controllers/Settings/CollectionProgrammingController.php index 39219afe..7a03883b 100644 --- a/app/Http/Controllers/Settings/CollectionProgrammingController.php +++ b/app/Http/Controllers/Settings/CollectionProgrammingController.php @@ -95,7 +95,7 @@ class CollectionProgrammingController extends Controller 'title' => 'Collection Programming — Skinbase', 'description' => 'Staff programming tools for assignments, previews, eligibility diagnostics, and recommendation refreshes.', 'canonical' => route('staff.collections.programming'), - 'robots' => 'noindex,follow', + 'robots' => 'index,follow', ], ])->rootView('collections'); } diff --git a/app/Http/Controllers/Settings/CollectionSurfaceController.php b/app/Http/Controllers/Settings/CollectionSurfaceController.php index 3994f55a..2ada671d 100644 --- a/app/Http/Controllers/Settings/CollectionSurfaceController.php +++ b/app/Http/Controllers/Settings/CollectionSurfaceController.php @@ -69,7 +69,7 @@ class CollectionSurfaceController extends Controller 'title' => 'Collection Surfaces - Skinbase', 'description' => 'Staff tools for homepage, discovery, and campaign collection surfaces.', 'canonical' => route('settings.collections.surfaces.index'), - 'robots' => 'noindex,follow', + 'robots' => 'index,follow', ], ])->rootView('collections'); } diff --git a/app/Http/Controllers/Settings/FeaturedArtworkAdminController.php b/app/Http/Controllers/Settings/FeaturedArtworkAdminController.php index 7e5be798..1b05bf77 100644 --- a/app/Http/Controllers/Settings/FeaturedArtworkAdminController.php +++ b/app/Http/Controllers/Settings/FeaturedArtworkAdminController.php @@ -46,7 +46,7 @@ class FeaturedArtworkAdminController extends Controller 'title' => 'Featured Artworks — Skinbase', 'description' => 'Editorial controls for homepage featured artworks and the current hero winner.', 'canonical' => route($routePrefix . 'main'), - 'robots' => 'noindex,follow', + 'robots' => 'index,follow', ], ], ))->rootView($isAdminSurface ? 'admin' : 'collections'); diff --git a/app/Http/Controllers/StoryController.php b/app/Http/Controllers/StoryController.php index cb26e0ca..848e5dc4 100644 --- a/app/Http/Controllers/StoryController.php +++ b/app/Http/Controllers/StoryController.php @@ -194,7 +194,7 @@ class StoryController extends Controller 'storyTypes' => $this->storyCategories(), 'page_title' => 'Create Story - Skinbase', 'page_meta_description' => 'Write and publish a creator story on Skinbase.', - 'page_robots' => 'noindex,nofollow', + 'page_robots' => 'index,nofollow', ]); } diff --git a/app/Http/Controllers/Web/ArtworkPageController.php b/app/Http/Controllers/Web/ArtworkPageController.php index 00303450..5af3ef10 100644 --- a/app/Http/Controllers/Web/ArtworkPageController.php +++ b/app/Http/Controllers/Web/ArtworkPageController.php @@ -149,7 +149,7 @@ final class ArtworkPageController extends Controller 'md' => $thumbMd, 'lg' => $thumbLg, 'xl' => $thumbXl, - ], $canonical)->toArray(); + ], $canonical, $this->artworkBreadcrumbs($artwork, $canonical))->toArray(); $categoryIds = $artwork->categories->pluck('id')->filter()->values(); $tagIds = $artwork->tags->pluck('id')->filter()->values(); @@ -364,6 +364,70 @@ final class ArtworkPageController extends Controller } } + /** + * @return array + */ + private function artworkBreadcrumbs(Artwork $artwork, string $canonical): array + { + $primaryCategory = $artwork->categories + ->sortBy(fn ($category) => [ + (int) ($category->sort_order ?? 0), + (string) ($category->name ?? ''), + ]) + ->first(); + + if ($primaryCategory === null) { + return [ + ['name' => 'Explore', 'url' => url('/explore')], + ['name' => html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'url' => $canonical], + ]; + } + + $contentType = $primaryCategory->contentType; + $chain = collect(); + $current = $primaryCategory; + + while ($current !== null) { + $chain->prepend($current); + $current = $current->relationLoaded('parent') ? $current->parent : null; + } + + $breadcrumbs = []; + $contentTypeSlug = trim((string) ($contentType?->slug ?? '')); + $contentTypeName = trim((string) ($contentType?->name ?? '')); + + if ($contentTypeSlug !== '' && $contentTypeName !== '') { + $breadcrumbs[] = [ + 'name' => $contentTypeName, + 'url' => url('/' . $contentTypeSlug), + ]; + } + + $pathSegments = []; + + foreach ($chain as $category) { + $slug = trim((string) ($category->slug ?? '')); + $name = trim((string) ($category->name ?? '')); + + if ($slug === '' || $name === '' || $contentTypeSlug === '') { + continue; + } + + $pathSegments[] = $slug; + $breadcrumbs[] = [ + 'name' => $name, + 'url' => url('/' . $contentTypeSlug . '/' . implode('/', $pathSegments)), + ]; + } + + $breadcrumbs[] = [ + 'name' => html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'), + 'url' => $canonical, + ]; + + return $breadcrumbs; + } + /** Silently catch suggestion query failures so error page never crashes. */ private function safeSuggestions(callable $fn): mixed { diff --git a/app/Http/Controllers/Web/BrowseGalleryController.php b/app/Http/Controllers/Web/BrowseGalleryController.php index 629bbd78..98b19048 100644 --- a/app/Http/Controllers/Web/BrowseGalleryController.php +++ b/app/Http/Controllers/Web/BrowseGalleryController.php @@ -181,7 +181,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller 'hero_title' => $contentType->name, 'hero_description' => $contentType->description ?? ($contentType->name . ' artworks on Skinbase.'), 'breadcrumbs' => collect([ - (object) ['name' => 'Explore', 'url' => '/browse'], + (object) ['name' => 'Explore', 'url' => route('explore.index')], (object) ['name' => $contentType->name, 'url' => '/' . $contentSlug], ]), 'page_title' => $contentType->name . ' – Skinbase', @@ -237,7 +237,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller $breadcrumbs = collect(array_merge([ (object) [ 'name' => 'Explore', - 'url' => '/browse', + 'url' => route('explore.index'), ], (object) [ 'name' => $contentType->name, @@ -335,6 +335,8 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller return (object) $this->maturity->decoratePayload([ 'id' => $artwork->id, 'name' => $artwork->title, + 'slug' => $artwork->slug, + 'url' => route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug]), 'content_type_name' => $primaryCategory?->contentType?->name ?? '', 'content_type_slug' => $primaryCategory?->contentType?->slug ?? '', 'category_name' => $primaryCategory->name ?? '', diff --git a/app/Http/Controllers/Web/SimilarArtworksPageController.php b/app/Http/Controllers/Web/SimilarArtworksPageController.php index 5b33e6da..f074cf31 100644 --- a/app/Http/Controllers/Web/SimilarArtworksPageController.php +++ b/app/Http/Controllers/Web/SimilarArtworksPageController.php @@ -102,7 +102,7 @@ final class SimilarArtworksPageController extends Controller 'page_title' => 'Similar to "' . $sourceTitle . '" — Skinbase', 'page_meta_description' => 'Discover artworks similar to "' . $sourceTitle . '" on Skinbase.', 'page_canonical' => $baseUrl, - 'page_robots' => 'noindex,follow', + 'page_robots' => 'index,follow', 'breadcrumbs' => collect([ (object) ['name' => 'Explore', 'url' => '/explore'], (object) ['name' => $sourceTitle, 'url' => $sourceUrl], diff --git a/app/Http/Requests/Academy/UpsertAcademyCourseRequest.php b/app/Http/Requests/Academy/UpsertAcademyCourseRequest.php new file mode 100644 index 00000000..d708ed21 --- /dev/null +++ b/app/Http/Requests/Academy/UpsertAcademyCourseRequest.php @@ -0,0 +1,53 @@ +user()?->hasStaffAccess(); + } + + protected function prepareForValidation(): void + { + $this->merge([ + 'is_featured' => $this->boolean('is_featured'), + 'order_num' => $this->filled('order_num') ? (int) $this->input('order_num') : 0, + 'estimated_minutes' => $this->filled('estimated_minutes') ? (int) $this->input('estimated_minutes') : null, + ]); + } + + public function rules(): array + { + $courseId = $this->route('academyCourse')?->id; + + return [ + 'title' => ['required', 'string', 'max:180'], + 'slug' => ['required', 'string', 'max:180', Rule::unique('academy_courses', 'slug')->ignore($courseId)], + 'subtitle' => ['nullable', 'string', 'max:255'], + 'excerpt' => ['nullable', 'string'], + 'description' => ['nullable', 'string'], + 'cover_image' => ['nullable', 'string', 'max:2048'], + 'teaser_image' => ['nullable', 'string', 'max:2048'], + 'access_level' => ['required', 'string', Rule::in(['free', 'premium', 'mixed'])], + 'difficulty' => ['required', 'string', Rule::in(['beginner', 'intermediate', 'advanced'])], + 'status' => ['required', 'string', Rule::in(['draft', 'review', 'published', 'archived'])], + 'is_featured' => ['required', 'boolean'], + 'order_num' => ['required', 'integer', 'min:0'], + 'estimated_minutes' => ['nullable', 'integer', 'min:1', 'max:10000'], + 'published_at' => ['nullable', 'date'], + 'seo_title' => ['nullable', 'string', 'max:180'], + 'seo_description' => ['nullable', 'string', 'max:255'], + 'meta_keywords' => ['nullable', 'string'], + 'og_title' => ['nullable', 'string', 'max:180'], + 'og_description' => ['nullable', 'string', 'max:255'], + 'og_image' => ['nullable', 'string', 'max:2048'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/Academy/UpsertAcademyLessonRequest.php b/app/Http/Requests/Academy/UpsertAcademyLessonRequest.php index 27654db2..793f3c4e 100644 --- a/app/Http/Requests/Academy/UpsertAcademyLessonRequest.php +++ b/app/Http/Requests/Academy/UpsertAcademyLessonRequest.php @@ -69,6 +69,18 @@ class UpsertAcademyLessonRequest extends FormRequest ->all(); $this->merge([ + 'lesson_number' => $this->filled('lesson_number') ? (int) $this->input('lesson_number') : null, + 'course_order' => $this->filled('course_order') ? (int) $this->input('course_order') : null, + 'content_source' => in_array((string) $this->input('content_source'), ['html', 'markdown'], true) + ? (string) $this->input('content_source') + : ($this->filled('content_markdown') ? 'markdown' : 'html'), + 'course_ids' => collect($this->input('course_ids', [])) + ->filter(static fn ($courseId): bool => filled($courseId)) + ->map(static fn ($courseId): int => (int) $courseId) + ->unique() + ->values() + ->all(), + 'tags' => array_values(array_filter((array) $this->input('tags', []))), 'reading_minutes' => $this->filled('reading_minutes') ? (int) $this->input('reading_minutes') : 5, 'featured' => $this->boolean('featured'), 'active' => $this->boolean('active', true), @@ -84,12 +96,22 @@ class UpsertAcademyLessonRequest extends FormRequest 'category_id' => ['nullable', 'integer', 'exists:academy_categories,id'], 'title' => ['required', 'string', 'max:180'], 'slug' => ['required', 'string', 'max:180', Rule::unique('academy_lessons', 'slug')->ignore($lessonId)], + 'lesson_number' => ['nullable', 'integer', 'min:1'], + 'course_order' => ['nullable', 'integer', 'min:1'], + 'course_ids' => ['nullable', 'array'], + 'course_ids.*' => ['integer', 'exists:academy_courses,id'], + 'series_name' => ['nullable', 'string', 'max:120'], 'excerpt' => ['nullable', 'string'], 'content' => ['nullable', 'string'], + 'content_markdown' => ['nullable', 'string'], + 'content_source' => ['required', 'string', Rule::in(['html', 'markdown'])], 'difficulty' => ['required', 'string', Rule::in((array) config('academy.difficulty_levels', []))], 'access_level' => ['required', 'string', Rule::in(['free', 'creator', 'pro'])], 'lesson_type' => ['required', 'string', 'max:80'], 'cover_image' => ['nullable', 'string', 'max:2048'], + 'article_cover_image' => ['nullable', 'string', 'max:2048'], + 'tags' => ['nullable', 'array'], + 'tags.*' => ['string', 'max:60'], 'video_url' => ['nullable', 'string', 'max:2048'], 'reading_minutes' => ['required', 'integer', 'min:1', 'max:999'], 'featured' => ['required', 'boolean'], diff --git a/app/Http/Requests/Academy/UpsertAcademyPromptTemplateRequest.php b/app/Http/Requests/Academy/UpsertAcademyPromptTemplateRequest.php index 7f22fa14..3226b82b 100644 --- a/app/Http/Requests/Academy/UpsertAcademyPromptTemplateRequest.php +++ b/app/Http/Requests/Academy/UpsertAcademyPromptTemplateRequest.php @@ -20,8 +20,31 @@ class UpsertAcademyPromptTemplateRequest extends FormRequest 'featured' => $this->boolean('featured'), 'prompt_of_week' => $this->boolean('prompt_of_week'), 'active' => $this->boolean('active', true), + 'new_category_name' => trim((string) $this->input('new_category_name', '')), 'tags' => array_values(array_filter((array) $this->input('tags', []))), - 'tool_notes' => (array) $this->input('tool_notes', []), + 'tool_notes' => collect($this->input('tool_notes', [])) + ->filter(static fn ($note): bool => is_array($note) || is_string($note)) + ->map(function ($note): array|string { + if (is_string($note)) { + return $note; + } + + return [ + 'provider' => $note['provider'] ?? null, + 'model_name' => $note['model_name'] ?? null, + 'notes' => $note['notes'] ?? null, + 'strengths' => $note['strengths'] ?? null, + 'weaknesses' => $note['weaknesses'] ?? null, + 'best_for' => $note['best_for'] ?? null, + 'image_path' => $note['image_path'] ?? null, + 'thumb_path' => $note['thumb_path'] ?? null, + 'settings' => $note['settings'] ?? null, + 'score' => filled($note['score'] ?? null) ? (int) $note['score'] : null, + 'active' => filter_var($note['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true, + ]; + }) + ->values() + ->all(), ]); } @@ -31,6 +54,7 @@ class UpsertAcademyPromptTemplateRequest extends FormRequest return [ 'category_id' => ['nullable', 'integer', 'exists:academy_categories,id'], + 'new_category_name' => ['nullable', 'string', 'max:120'], 'title' => ['required', 'string', 'max:180'], 'slug' => ['required', 'string', 'max:180', Rule::unique('academy_prompt_templates', 'slug')->ignore($promptId)], 'excerpt' => ['nullable', 'string'], @@ -44,6 +68,17 @@ class UpsertAcademyPromptTemplateRequest extends FormRequest 'tags' => ['nullable', 'array'], 'tags.*' => ['string', 'max:60'], 'tool_notes' => ['nullable', 'array'], + 'tool_notes.*.provider' => ['nullable', 'string', 'max:100'], + 'tool_notes.*.model_name' => ['nullable', 'string', 'max:150'], + 'tool_notes.*.notes' => ['nullable', 'string'], + 'tool_notes.*.strengths' => ['nullable', 'string'], + 'tool_notes.*.weaknesses' => ['nullable', 'string'], + 'tool_notes.*.best_for' => ['nullable', 'string'], + 'tool_notes.*.image_path' => ['nullable', 'string', 'max:500'], + 'tool_notes.*.thumb_path' => ['nullable', 'string', 'max:500'], + 'tool_notes.*.settings' => ['nullable', 'string'], + 'tool_notes.*.score' => ['nullable', 'integer', 'min:1', 'max:10'], + 'tool_notes.*.active' => ['nullable', 'boolean'], 'preview_image' => ['nullable', 'string', 'max:2048'], 'preview_image_file' => ['nullable', 'file', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120'], 'featured' => ['required', 'boolean'], diff --git a/app/Models/AcademyCourse.php b/app/Models/AcademyCourse.php new file mode 100644 index 00000000..1f14a48a --- /dev/null +++ b/app/Models/AcademyCourse.php @@ -0,0 +1,170 @@ + 'boolean', + 'order_num' => 'integer', + 'estimated_minutes' => 'integer', + 'lessons_count_cache' => 'integer', + 'published_at' => 'datetime', + ]; + + public function scopePublished(Builder $query): Builder + { + return $query + ->where('status', self::STATUS_PUBLISHED) + ->where(function (Builder $builder): void { + $builder->whereNull('published_at')->orWhere('published_at', '<=', now()); + }); + } + + public function scopeFeatured(Builder $query): Builder + { + return $query->where('is_featured', true); + } + + public function scopeOrdered(Builder $query): Builder + { + return $query + ->orderByDesc('is_featured') + ->orderBy('order_num') + ->orderByDesc('published_at') + ->orderBy('id'); + } + + public function scopeFree(Builder $query): Builder + { + return $query->where('access_level', 'free'); + } + + public function scopePremium(Builder $query): Builder + { + return $query->where('access_level', 'premium'); + } + + public function scopeMixed(Builder $query): Builder + { + return $query->where('access_level', 'mixed'); + } + + public function sections(): HasMany + { + return $this->hasMany(AcademyCourseSection::class, 'course_id') + ->orderBy('order_num') + ->orderBy('id'); + } + + public function courseLessons(): HasMany + { + return $this->hasMany(AcademyCourseLesson::class, 'course_id') + ->orderBy('order_num') + ->orderBy('id'); + } + + public function lessons(): BelongsToMany + { + return $this->belongsToMany(AcademyLesson::class, 'academy_course_lessons', 'course_id', 'lesson_id') + ->using(AcademyCourseLesson::class) + ->withPivot(['section_id', 'order_num', 'is_required', 'access_override', 'unlock_after_lesson_id']) + ->withTimestamps() + ->orderBy('academy_course_lessons.order_num') + ->orderBy('academy_course_lessons.id'); + } + + public function enrollments(): HasMany + { + return $this->hasMany(AcademyCourseEnrollment::class, 'course_id'); + } + + public function isPublished(): bool + { + return (string) $this->status === self::STATUS_PUBLISHED + && ($this->published_at === null || $this->published_at->lte(now())); + } + + public function isFree(): bool + { + return (string) $this->access_level === 'free'; + } + + public function isPremium(): bool + { + return (string) $this->access_level === 'premium'; + } + + public function isMixed(): bool + { + return (string) $this->access_level === 'mixed'; + } + + public function getPublicUrl(): string + { + return route('academy.courses.show', ['course' => $this->slug]); + } + + public function getContinueUrl(?User $user): string + { + $lastLesson = $user?->academyCourseEnrollments() + ->where('course_id', $this->id) + ->with('lastLesson') + ->first()?->lastLesson; + + if ($lastLesson instanceof AcademyLesson) { + return route('academy.courses.lessons.show', ['course' => $this->slug, 'lesson' => $lastLesson->slug]); + } + + $firstLesson = $this->courseLessons() + ->with('lesson') + ->get() + ->map(fn (AcademyCourseLesson $courseLesson): ?AcademyLesson => $courseLesson->lesson) + ->first(fn (?AcademyLesson $lesson): bool => $lesson instanceof AcademyLesson); + + if ($firstLesson instanceof AcademyLesson) { + return route('academy.courses.lessons.show', ['course' => $this->slug, 'lesson' => $firstLesson->slug]); + } + + return $this->getPublicUrl(); + } +} \ No newline at end of file diff --git a/app/Models/AcademyCourseEnrollment.php b/app/Models/AcademyCourseEnrollment.php new file mode 100644 index 00000000..654fdf3c --- /dev/null +++ b/app/Models/AcademyCourseEnrollment.php @@ -0,0 +1,44 @@ + 'datetime', + 'completed_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function course(): BelongsTo + { + return $this->belongsTo(AcademyCourse::class, 'course_id'); + } + + public function lastLesson(): BelongsTo + { + return $this->belongsTo(AcademyLesson::class, 'last_lesson_id'); + } +} \ No newline at end of file diff --git a/app/Models/AcademyCourseLesson.php b/app/Models/AcademyCourseLesson.php new file mode 100644 index 00000000..228d7eb2 --- /dev/null +++ b/app/Models/AcademyCourseLesson.php @@ -0,0 +1,50 @@ + 'integer', + 'is_required' => 'boolean', + ]; + + public function course(): BelongsTo + { + return $this->belongsTo(AcademyCourse::class, 'course_id'); + } + + public function section(): BelongsTo + { + return $this->belongsTo(AcademyCourseSection::class, 'section_id'); + } + + public function lesson(): BelongsTo + { + return $this->belongsTo(AcademyLesson::class, 'lesson_id'); + } + + public function unlockAfterLesson(): BelongsTo + { + return $this->belongsTo(AcademyLesson::class, 'unlock_after_lesson_id'); + } +} \ No newline at end of file diff --git a/app/Models/AcademyCourseSection.php b/app/Models/AcademyCourseSection.php new file mode 100644 index 00000000..56a30463 --- /dev/null +++ b/app/Models/AcademyCourseSection.php @@ -0,0 +1,49 @@ + 'integer', + 'is_visible' => 'boolean', + ]; + + public function course(): BelongsTo + { + return $this->belongsTo(AcademyCourse::class, 'course_id'); + } + + public function courseLessons(): HasMany + { + return $this->hasMany(AcademyCourseLesson::class, 'section_id') + ->orderBy('order_num') + ->orderBy('id'); + } + + public function lessons(): BelongsToMany + { + return $this->belongsToMany(AcademyLesson::class, 'academy_course_lessons', 'section_id', 'lesson_id') + ->using(AcademyCourseLesson::class) + ->withPivot(['course_id', 'order_num', 'is_required', 'access_override', 'unlock_after_lesson_id']) + ->withTimestamps() + ->orderBy('academy_course_lessons.order_num') + ->orderBy('academy_course_lessons.id'); + } +} \ No newline at end of file diff --git a/app/Models/AcademyLesson.php b/app/Models/AcademyLesson.php index 0886b035..11ddf24c 100644 --- a/app/Models/AcademyLesson.php +++ b/app/Models/AcademyLesson.php @@ -6,6 +6,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; @@ -33,12 +34,18 @@ class AcademyLesson extends Model 'category_id', 'title', 'slug', + 'lesson_number', + 'course_order', + 'series_name', 'excerpt', 'content', + 'content_markdown', 'difficulty', 'access_level', 'lesson_type', 'cover_image', + 'article_cover_image', + 'tags', 'video_url', 'reading_minutes', 'featured', @@ -49,12 +56,20 @@ class AcademyLesson extends Model ]; protected $casts = [ + 'lesson_number' => 'integer', + 'course_order' => 'integer', + 'tags' => 'array', 'reading_minutes' => 'integer', 'featured' => 'boolean', 'active' => 'boolean', 'published_at' => 'datetime', ]; + protected $appends = [ + 'formatted_lesson_number', + 'lesson_label', + ]; + public function scopeActive(Builder $query): Builder { return $query->where('active', true); @@ -65,6 +80,17 @@ class AcademyLesson extends Model return $query->whereNotNull('published_at')->where('published_at', '<=', now()); } + public function scopeOrderedForCourse(Builder $query): Builder + { + return $query + ->orderByRaw('case when course_order is null then 1 else 0 end') + ->orderBy('course_order') + ->orderByRaw('case when lesson_number is null then 1 else 0 end') + ->orderBy('lesson_number') + ->orderByDesc('published_at') + ->orderBy('id'); + } + public function category(): BelongsTo { return $this->belongsTo(AcademyCategory::class, 'category_id'); @@ -75,6 +101,23 @@ class AcademyLesson extends Model return $this->hasMany(AcademyLessonProgress::class, 'lesson_id'); } + public function courseLessons(): HasMany + { + return $this->hasMany(AcademyCourseLesson::class, 'lesson_id') + ->orderBy('order_num') + ->orderBy('id'); + } + + public function courses(): BelongsToMany + { + return $this->belongsToMany(AcademyCourse::class, 'academy_course_lessons', 'lesson_id', 'course_id') + ->using(AcademyCourseLesson::class) + ->withPivot(['section_id', 'order_num', 'is_required', 'access_override', 'unlock_after_lesson_id']) + ->withTimestamps() + ->orderBy('academy_course_lessons.order_num') + ->orderBy('academy_course_lessons.id'); + } + public function blocks(): HasMany { return $this->hasMany(AcademyLessonBlock::class, 'lesson_id') @@ -86,4 +129,30 @@ class AcademyLesson extends Model { return $this->blocks()->where('active', true); } + + public function getFormattedLessonNumberAttribute(): ?string + { + if (! is_int($this->lesson_number) || $this->lesson_number < 1) { + return null; + } + + return sprintf('Lesson %02d', $this->lesson_number); + } + + public function getLessonLabelAttribute(): ?string + { + $formattedLessonNumber = $this->formatted_lesson_number; + + if ($formattedLessonNumber === null) { + return null; + } + + $seriesName = trim((string) ($this->series_name ?? '')); + + if ($seriesName === '') { + return $formattedLessonNumber; + } + + return sprintf('%s · %s', $seriesName, $formattedLessonNumber); + } } diff --git a/app/Models/AcademyLessonRevision.php b/app/Models/AcademyLessonRevision.php new file mode 100644 index 00000000..d7363821 --- /dev/null +++ b/app/Models/AcademyLessonRevision.php @@ -0,0 +1,32 @@ + 'array', + ]; + + public function lesson(): BelongsTo + { + return $this->belongsTo(AcademyLesson::class, 'lesson_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 90e656d8..0348a792 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -16,6 +16,7 @@ use App\Models\SocialAccount; use App\Models\Conversation; use App\Models\ConversationParticipant; use App\Models\AcademyBadge; +use App\Models\AcademyCourseEnrollment; use App\Models\AcademyChallengeSubmission; use App\Models\AcademyLessonProgress; use App\Models\AcademySavedPrompt; @@ -207,6 +208,11 @@ class User extends Authenticatable return $this->hasMany(AcademyLessonProgress::class, 'user_id'); } + public function academyCourseEnrollments(): HasMany + { + return $this->hasMany(AcademyCourseEnrollment::class, 'user_id'); + } + public function academySavedPrompts(): HasMany { return $this->hasMany(AcademySavedPrompt::class, 'user_id'); diff --git a/app/Services/Academy/AcademyAccessService.php b/app/Services/Academy/AcademyAccessService.php index b72b4882..1712644d 100644 --- a/app/Services/Academy/AcademyAccessService.php +++ b/app/Services/Academy/AcademyAccessService.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Services\Academy; +use App\Models\AcademyCourse; +use App\Models\AcademyCourseLesson; use App\Models\AcademyAiComparisonResult; use App\Models\AcademyChallenge; use App\Models\AcademyLesson; @@ -53,15 +55,31 @@ final class AcademyAccessService return $this->canAccessContent($user, (string) $challenge->access_level); } - public function lessonPayload(AcademyLesson $lesson, ?User $viewer, bool $includeFull = false): array + public function canAccessCourseLesson(?User $user, AcademyCourseLesson $courseLesson): bool { - $authorized = $this->canAccessLesson($viewer, $lesson); - $fullContent = (string) ($lesson->content ?? ''); + $accessLevel = trim((string) ($courseLesson->access_override ?: $courseLesson->lesson?->access_level ?: 'free')); + + if ($accessLevel === 'premium') { + return $user?->isAdmin() ?? false; + } + + return $this->canAccessContent($user, $accessLevel === 'mixed' ? 'free' : $accessLevel); + } + + public function lessonPayload(AcademyLesson $lesson, ?User $viewer, bool $includeFull = false, ?bool $authorizedOverride = null): array + { + $authorized = $authorizedOverride ?? $this->canAccessLesson($viewer, $lesson); + $fullContent = $this->injectHeadingIds((string) ($lesson->content ?? '')); return [ 'id' => (int) $lesson->id, 'title' => (string) $lesson->title, 'slug' => (string) $lesson->slug, + 'lesson_number' => $lesson->lesson_number, + 'formatted_lesson_number' => $lesson->formatted_lesson_number, + 'course_order' => $lesson->course_order, + 'series_name' => (string) ($lesson->series_name ?? ''), + 'lesson_label' => $lesson->lesson_label, 'excerpt' => (string) ($lesson->excerpt ?? ''), 'content' => ($authorized && $includeFull) ? $fullContent : null, 'content_preview' => $authorized ? null : $this->previewText($fullContent, 360), @@ -70,6 +88,9 @@ final class AcademyAccessService 'lesson_type' => (string) $lesson->lesson_type, 'cover_image' => $lesson->cover_image, 'cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($lesson->cover_image ?? '')), + 'article_cover_image' => $lesson->article_cover_image, + 'article_cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($lesson->article_cover_image ?? '')), + 'tags' => array_values((array) ($lesson->tags ?? [])), 'video_url' => $authorized ? $lesson->video_url : null, 'reading_minutes' => (int) $lesson->reading_minutes, 'featured' => (bool) $lesson->featured, @@ -88,6 +109,66 @@ final class AcademyAccessService ]; } + public function coursePayload(AcademyCourse $course, ?User $viewer, array $options = []): array + { + $progress = is_array($options['progress'] ?? null) ? $options['progress'] : null; + $lessonCount = (int) ($course->lessons_count_cache ?: $course->courseLessons()->count()); + + return [ + 'id' => (int) $course->id, + 'title' => (string) $course->title, + 'slug' => (string) $course->slug, + 'subtitle' => (string) ($course->subtitle ?? ''), + 'excerpt' => (string) ($course->excerpt ?? ''), + 'description' => (string) ($course->description ?? ''), + 'cover_image' => (string) ($course->cover_image ?? ''), + 'cover_image_url' => $this->resolveLessonCoverImageUrl((string) ($course->cover_image ?? '')), + 'teaser_image' => (string) ($course->teaser_image ?? ''), + 'teaser_image_url' => $this->resolveLessonCoverImageUrl((string) ($course->teaser_image ?? '')), + 'access_level' => (string) $course->access_level, + 'difficulty' => (string) $course->difficulty, + 'status' => (string) $course->status, + 'is_featured' => (bool) $course->is_featured, + 'order_num' => (int) ($course->order_num ?? 0), + 'estimated_minutes' => (int) ($course->estimated_minutes ?? 0), + 'lessons_count' => $lessonCount, + 'published_at' => $course->published_at?->toISOString(), + 'public_url' => route('academy.courses.show', ['course' => $course->slug]), + 'progress' => $progress ? [ + 'completedRequired' => (int) ($progress['completed_required'] ?? 0), + 'totalRequired' => (int) ($progress['total_required'] ?? 0), + 'percent' => (int) ($progress['progress_percent'] ?? 0), + 'completed' => (bool) ($progress['completed'] ?? false), + ] : null, + 'continue_url' => $viewer ? $course->getContinueUrl($viewer) : route('academy.courses.show', ['course' => $course->slug]), + ]; + } + + public function courseLessonPayload(AcademyCourseLesson $courseLesson, ?User $viewer, bool $includeFull = false, array $options = []): array + { + $lesson = $courseLesson->lesson; + + if (! $lesson instanceof AcademyLesson) { + return []; + } + + $authorized = $this->canAccessCourseLesson($viewer, $courseLesson); + $payload = $this->lessonPayload($lesson, $viewer, $includeFull, $authorized); + + $payload['course_lesson_id'] = (int) $courseLesson->id; + $payload['course_id'] = (int) $courseLesson->course_id; + $payload['section_id'] = $courseLesson->section_id ? (int) $courseLesson->section_id : null; + $payload['order_num'] = (int) ($courseLesson->order_num ?? 0); + $payload['is_required'] = (bool) $courseLesson->is_required; + $payload['access_override'] = $courseLesson->access_override; + $payload['course_step_number'] = (int) ($options['course_step_number'] ?? 0); + $payload['course_step_label'] = (string) ($options['course_step_label'] ?? ''); + $payload['completed'] = in_array((int) $courseLesson->lesson_id, array_map('intval', (array) ($options['completed_lesson_ids'] ?? [])), true); + $payload['course_url'] = route('academy.courses.lessons.show', ['course' => $courseLesson->course->slug, 'lesson' => $lesson->slug]); + + return $payload; + } + public function promptPayload(AcademyPromptTemplate $prompt, ?User $viewer, bool $includeFull = false): array { $authorized = $this->canAccessPrompt($viewer, $prompt); @@ -106,7 +187,7 @@ final class AcademyAccessService 'access_level' => (string) $prompt->access_level, 'aspect_ratio' => $prompt->aspect_ratio, 'tags' => array_values((array) ($prompt->tags ?? [])), - 'tool_notes' => $authorized ? (array) ($prompt->tool_notes ?? []) : [], + 'tool_notes' => $authorized ? $this->promptToolNotesPayload((array) ($prompt->tool_notes ?? [])) : [], 'preview_image' => $this->resolvePreviewImageUrl((string) ($prompt->preview_image ?? '')), 'featured' => (bool) $prompt->featured, 'prompt_of_week' => (bool) $prompt->prompt_of_week, @@ -121,6 +202,48 @@ final class AcademyAccessService ]; } + /** + * @param array $notes + * @return array> + */ + private function promptToolNotesPayload(array $notes): array + { + return collect($notes) + ->filter(static fn ($note): bool => is_array($note)) + ->map(function (array $note): array { + return [ + 'provider' => trim((string) ($note['provider'] ?? '')), + 'model_name' => trim((string) ($note['model_name'] ?? '')), + 'notes' => trim((string) ($note['notes'] ?? '')), + 'strengths' => trim((string) ($note['strengths'] ?? '')), + 'weaknesses' => trim((string) ($note['weaknesses'] ?? '')), + 'best_for' => trim((string) ($note['best_for'] ?? '')), + 'image_path' => trim((string) ($note['image_path'] ?? '')), + 'image_url' => $this->resolveLessonMediaUrl((string) ($note['image_path'] ?? '')), + 'thumb_path' => trim((string) ($note['thumb_path'] ?? '')), + 'thumb_url' => $this->resolveLessonMediaUrl((string) ($note['thumb_path'] ?? '')), + 'settings' => trim((string) ($note['settings'] ?? '')), + 'score' => filled($note['score'] ?? null) ? (int) $note['score'] : null, + 'active' => filter_var($note['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true, + ]; + }) + ->filter(function (array $note): bool { + return collect([ + $note['provider'], + $note['model_name'], + $note['notes'], + $note['strengths'], + $note['weaknesses'], + $note['best_for'], + $note['image_path'], + $note['thumb_path'], + $note['settings'], + ])->contains(fn (string $value): bool => $value !== '') || $note['score'] !== null; + }) + ->values() + ->all(); + } + public function packPayload(AcademyPromptPack $pack, ?User $viewer, bool $includePrompts = false): array { $authorized = $this->canAccessPack($viewer, $pack); @@ -190,6 +313,7 @@ final class AcademyAccessService { return match (Str::lower(trim($accessLevel))) { 'admin' => 99, + 'premium' => 40, 'pro' => 30, 'creator' => 20, default => 10, @@ -322,4 +446,44 @@ final class AcademyAccessService 'comparison_results' => $results, ]; } + + private function injectHeadingIds(string $html): string + { + if ($html === '') { + return $html; + } + + $seenIds = []; + + return preg_replace_callback( + '/<(h[23])([^>]*)>(.*?)<\/\1>/si', + function (array $m) use (&$seenIds): string { + [, $tag, $attrs, $inner] = $m; + + if (preg_match('/\bid\s*=/i', $attrs)) { + return $m[0]; + } + + $textContent = strip_tags($inner); + $baseId = $this->slugifyHeading($textContent); + $count = $seenIds[$baseId] ?? 0; + $id = $count > 0 ? $baseId.'-'.($count + 1) : $baseId; + $seenIds[$baseId] = $count + 1; + + return "<{$tag} id=\"{$id}\"{$attrs}>{$inner}"; + }, + $html + ) ?? $html; + } + + private function slugifyHeading(string $text): string + { + $slug = mb_strtolower($text); + $slug = preg_replace('/[^a-z0-9_\s\-]/', '', $slug) ?? ''; + $slug = preg_replace('/\s+/', '-', trim($slug)) ?? ''; + $slug = preg_replace('/-+/', '-', $slug) ?? ''; + $slug = trim($slug, '-'); + + return $slug !== '' ? $slug : 'section'; + } } diff --git a/app/Services/Academy/AcademyCacheService.php b/app/Services/Academy/AcademyCacheService.php index beaa6535..b60546eb 100644 --- a/app/Services/Academy/AcademyCacheService.php +++ b/app/Services/Academy/AcademyCacheService.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Services\Academy; use App\Models\AcademyCategory; +use App\Models\AcademyCourse; use App\Models\AcademyChallenge; use App\Models\AcademyLesson; use App\Models\AcademyPromptTemplate; @@ -14,6 +15,8 @@ final class AcademyCacheService { private const HOME_KEY = 'academy.home'; private const FEATURED_LESSONS_KEY = 'academy.featured_lessons'; + private const FEATURED_COURSES_KEY = 'academy.featured_courses'; + private const PUBLISHED_COURSES_KEY = 'academy.published_courses'; private const FEATURED_PROMPTS_KEY = 'academy.featured_prompts'; private const FEATURED_CHALLENGES_KEY = 'academy.featured_challenges'; private const CATEGORIES_KEY = 'academy.categories'; @@ -30,12 +33,32 @@ final class AcademyCacheService ->active() ->published() ->where('featured', true) - ->latest('published_at') + ->orderedForCourse() ->limit(6) ->get() ->all()); } + public function featuredCourses(): array + { + return Cache::remember(self::FEATURED_COURSES_KEY, $this->ttl(), static fn (): array => AcademyCourse::query() + ->published() + ->featured() + ->ordered() + ->limit(6) + ->get() + ->all()); + } + + public function publishedCourses(): array + { + return Cache::remember(self::PUBLISHED_COURSES_KEY, $this->ttl(), static fn (): array => AcademyCourse::query() + ->published() + ->ordered() + ->get() + ->all()); + } + public function featuredPrompts(): array { return Cache::remember(self::FEATURED_PROMPTS_KEY, $this->ttl(), static fn (): array => AcademyPromptTemplate::query() @@ -79,6 +102,8 @@ final class AcademyCacheService { Cache::forget(self::HOME_KEY); Cache::forget(self::FEATURED_LESSONS_KEY); + Cache::forget(self::FEATURED_COURSES_KEY); + Cache::forget(self::PUBLISHED_COURSES_KEY); Cache::forget(self::FEATURED_PROMPTS_KEY); Cache::forget(self::FEATURED_CHALLENGES_KEY); diff --git a/app/Services/Academy/AcademyCourseLessonOrderingService.php b/app/Services/Academy/AcademyCourseLessonOrderingService.php new file mode 100644 index 00000000..ee4b86d5 --- /dev/null +++ b/app/Services/Academy/AcademyCourseLessonOrderingService.php @@ -0,0 +1,52 @@ +id : (int) $course; + + if ($courseId < 1) { + return; + } + + AcademyCourseLesson::query() + ->with('lesson:id,lesson_number,course_order') + ->where('course_id', $courseId) + ->orderBy('order_num') + ->orderBy('id') + ->get() + ->values() + ->each(function (AcademyCourseLesson $courseLesson, int $index): void { + $pivotOrder = $index; + $lessonOrder = $index + 1; + + if ((int) ($courseLesson->order_num ?? 0) !== $pivotOrder) { + $courseLesson->forceFill([ + 'order_num' => $pivotOrder, + ])->save(); + } + + if ($courseLesson->lesson === null) { + return; + } + + if ((int) ($courseLesson->lesson->course_order ?? 0) === $lessonOrder + && (int) ($courseLesson->lesson->lesson_number ?? 0) === $lessonOrder) { + return; + } + + $courseLesson->lesson->forceFill([ + 'course_order' => $lessonOrder, + 'lesson_number' => $lessonOrder, + ])->save(); + }); + } +} \ No newline at end of file diff --git a/app/Services/Academy/AcademyCourseNavigationService.php b/app/Services/Academy/AcademyCourseNavigationService.php new file mode 100644 index 00000000..d6505631 --- /dev/null +++ b/app/Services/Academy/AcademyCourseNavigationService.php @@ -0,0 +1,72 @@ + + */ + public function orderedCourseLessons(AcademyCourse $course): Collection + { + return $course->courseLessons() + ->with(['section', 'lesson.category']) + ->get() + ->filter(fn (AcademyCourseLesson $courseLesson): bool => $courseLesson->lesson instanceof AcademyLesson + && (bool) $courseLesson->lesson->active + && $courseLesson->lesson->published_at !== null + && $courseLesson->lesson->published_at->lte(now())) + ->sort(fn (AcademyCourseLesson $left, AcademyCourseLesson $right): int => $this->courseLessonSortKey($left) <=> $this->courseLessonSortKey($right)) + ->values(); + } + + /** + * @return array + */ + private function courseLessonSortKey(AcademyCourseLesson $courseLesson): array + { + $section = $courseLesson->section; + + return [ + $courseLesson->section_id === null ? 0 : 1, + (int) ($section?->order_num ?? 0), + (int) ($section?->id ?? 0), + (int) ($courseLesson->order_num ?? 0), + (int) $courseLesson->id, + ]; + } + + public function firstPublishedLesson(AcademyCourse $course): ?AcademyCourseLesson + { + return $this->orderedCourseLessons($course)->first(); + } + + public function findCourseLesson(AcademyCourse $course, AcademyLesson $lesson): ?AcademyCourseLesson + { + return $this->orderedCourseLessons($course) + ->first(fn (AcademyCourseLesson $courseLesson): bool => $courseLesson->lesson?->is($lesson) ?? false); + } + + public function nextLesson(AcademyCourse $course, AcademyLesson $lesson): ?AcademyCourseLesson + { + $ordered = $this->orderedCourseLessons($course); + $index = $ordered->search(fn (AcademyCourseLesson $courseLesson): bool => $courseLesson->lesson?->is($lesson) ?? false); + + return is_int($index) ? $ordered->get($index + 1) : null; + } + + public function previousLesson(AcademyCourse $course, AcademyLesson $lesson): ?AcademyCourseLesson + { + $ordered = $this->orderedCourseLessons($course); + $index = $ordered->search(fn (AcademyCourseLesson $courseLesson): bool => $courseLesson->lesson?->is($lesson) ?? false); + + return is_int($index) && $index > 0 ? $ordered->get($index - 1) : null; + } +} \ No newline at end of file diff --git a/app/Services/Academy/AcademyCourseProgressService.php b/app/Services/Academy/AcademyCourseProgressService.php new file mode 100644 index 00000000..e5fa0b8d --- /dev/null +++ b/app/Services/Academy/AcademyCourseProgressService.php @@ -0,0 +1,173 @@ +getTotalRequiredLessonsCount($course); + $completedRequired = $user ? $this->getCompletedRequiredLessonsCount($user, $course) : 0; + $enrollment = $user + ? AcademyCourseEnrollment::query()->with('lastLesson')->where('user_id', $user->id)->where('course_id', $course->id)->first() + : null; + + return [ + 'total_required' => $totalRequired, + 'completed_required' => $completedRequired, + 'progress_percent' => $this->getProgressPercent($user, $course), + 'enrollment' => $enrollment, + 'next_lesson' => $user ? $this->getNextLesson($user, $course) : null, + 'continue_lesson' => $user ? $this->getContinueLesson($user, $course) : null, + 'completed' => $enrollment?->status === AcademyCourseEnrollment::STATUS_COMPLETED, + ]; + } + + public function getCompletedRequiredLessonsCount(User $user, AcademyCourse $course): int + { + $lessonIds = $course->courseLessons() + ->where('is_required', true) + ->pluck('lesson_id') + ->filter() + ->values() + ->all(); + + if ($lessonIds === []) { + return 0; + } + + return AcademyLessonProgress::query() + ->where('user_id', $user->id) + ->whereIn('lesson_id', $lessonIds) + ->whereNotNull('completed_at') + ->count(); + } + + public function getCompletedLessonIds(User $user, AcademyCourse $course): array + { + $lessonIds = $course->courseLessons() + ->pluck('lesson_id') + ->filter() + ->map(fn ($lessonId): int => (int) $lessonId) + ->values() + ->all(); + + if ($lessonIds === []) { + return []; + } + + return AcademyLessonProgress::query() + ->where('user_id', $user->id) + ->whereIn('lesson_id', $lessonIds) + ->whereNotNull('completed_at') + ->pluck('lesson_id') + ->map(fn ($lessonId): int => (int) $lessonId) + ->values() + ->all(); + } + + public function getTotalRequiredLessonsCount(AcademyCourse $course): int + { + return $course->courseLessons()->where('is_required', true)->count(); + } + + public function getProgressPercent(?User $user, AcademyCourse $course): int + { + if (! $user) { + return 0; + } + + $totalRequired = $this->getTotalRequiredLessonsCount($course); + + if ($totalRequired < 1) { + return 0; + } + + return (int) floor(($this->getCompletedRequiredLessonsCount($user, $course) / $totalRequired) * 100); + } + + public function getNextLesson(User $user, AcademyCourse $course): ?AcademyCourseLesson + { + $completedLessonIds = $this->getCompletedLessonIds($user, $course); + + return $this->navigation->orderedCourseLessons($course) + ->first(fn (AcademyCourseLesson $courseLesson): bool => ! in_array((int) $courseLesson->lesson_id, $completedLessonIds, true)); + } + + public function getPreviousLesson(AcademyCourse $course, AcademyLesson $lesson): ?AcademyCourseLesson + { + return $this->navigation->previousLesson($course, $lesson); + } + + public function getContinueLesson(User $user, AcademyCourse $course): ?AcademyCourseLesson + { + $enrollment = AcademyCourseEnrollment::query() + ->where('user_id', $user->id) + ->where('course_id', $course->id) + ->first(); + + if ($enrollment?->last_lesson_id) { + $lastLesson = AcademyLesson::query()->find($enrollment->last_lesson_id); + + if ($lastLesson instanceof AcademyLesson) { + $nextLesson = $this->navigation->nextLesson($course, $lastLesson); + + return $nextLesson ?? $this->navigation->findCourseLesson($course, $lastLesson); + } + } + + return $this->getNextLesson($user, $course) ?? $this->navigation->firstPublishedLesson($course); + } + + public function markEnrollmentStarted(User $user, AcademyCourse $course): AcademyCourseEnrollment + { + return AcademyCourseEnrollment::query()->updateOrCreate( + [ + 'user_id' => $user->id, + 'course_id' => $course->id, + ], + [ + 'status' => AcademyCourseEnrollment::STATUS_ACTIVE, + 'started_at' => now(), + 'completed_at' => null, + ], + ); + } + + public function updateLastLesson(User $user, AcademyCourse $course, AcademyLesson $lesson): AcademyCourseEnrollment + { + $enrollment = $this->markEnrollmentStarted($user, $course); + $enrollment->forceFill([ + 'last_lesson_id' => $lesson->id, + ])->save(); + + return $enrollment; + } + + public function markCourseCompletedIfFinished(User $user, AcademyCourse $course): AcademyCourseEnrollment + { + $enrollment = $this->markEnrollmentStarted($user, $course); + $progressPercent = $this->getProgressPercent($user, $course); + $isComplete = $this->getTotalRequiredLessonsCount($course) > 0 && $progressPercent >= 100; + + $enrollment->forceFill([ + 'status' => $isComplete ? AcademyCourseEnrollment::STATUS_COMPLETED : AcademyCourseEnrollment::STATUS_ACTIVE, + 'completed_at' => $isComplete ? ($enrollment->completed_at ?? now()) : null, + ])->save(); + + return $enrollment; + } +} \ No newline at end of file diff --git a/app/Services/Academy/AcademyLessonMarkdownRenderer.php b/app/Services/Academy/AcademyLessonMarkdownRenderer.php new file mode 100644 index 00000000..4197214a --- /dev/null +++ b/app/Services/Academy/AcademyLessonMarkdownRenderer.php @@ -0,0 +1,42 @@ +converter()->convert($trimmed)->getContent()); + } + + private function converter(): MarkdownConverter + { + if ($this->converter instanceof MarkdownConverter) { + return $this->converter; + } + + $environment = new Environment([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new GithubFlavoredMarkdownExtension()); + + return $this->converter = new MarkdownConverter($environment); + } +} \ No newline at end of file diff --git a/app/Services/Academy/AcademyProgressService.php b/app/Services/Academy/AcademyProgressService.php index 291deeca..13c916db 100644 --- a/app/Services/Academy/AcademyProgressService.php +++ b/app/Services/Academy/AcademyProgressService.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Services\Academy; +use App\Models\AcademyCourse; use App\Models\AcademyLesson; use App\Models\AcademyLessonProgress; use App\Models\AcademyPromptTemplate; @@ -12,11 +13,13 @@ use App\Models\User; final class AcademyProgressService { - public function __construct(private readonly AcademyBadgeService $badges) - { + public function __construct( + private readonly AcademyBadgeService $badges, + private readonly AcademyCourseProgressService $courses, + ) { } - public function markLessonComplete(User $user, AcademyLesson $lesson): AcademyLessonProgress + public function markLessonComplete(User $user, AcademyLesson $lesson, ?AcademyCourse $course = null): AcademyLessonProgress { $progress = AcademyLessonProgress::query()->updateOrCreate( [ @@ -28,6 +31,11 @@ final class AcademyProgressService ], ); + if ($course instanceof AcademyCourse) { + $this->courses->updateLastLesson($user, $course, $lesson); + $this->courses->markCourseCompletedIfFinished($user, $course); + } + $this->badges->syncForUser($user); return $progress; diff --git a/app/Services/Sitemaps/Builders/AcademyCoursesSitemapBuilder.php b/app/Services/Sitemaps/Builders/AcademyCoursesSitemapBuilder.php new file mode 100644 index 00000000..529f8c4a --- /dev/null +++ b/app/Services/Sitemaps/Builders/AcademyCoursesSitemapBuilder.php @@ -0,0 +1,47 @@ +urls->staticRoute('/academy/courses')]; + + $details = AcademyCourse::query() + ->published() + ->orderBy('id') + ->cursor() + ->map(fn (AcademyCourse $course): SitemapUrl => $this->urls->staticRoute('/academy/courses/' . $course->slug, $course->updated_at ?? $course->published_at)) + ->values() + ->all(); + + return array_merge($items, $details); + } + + public function lastModified(): ?DateTimeInterface + { + return $this->dateTime(AcademyCourse::query()->published()->max('updated_at')); + } +} \ No newline at end of file diff --git a/app/Services/Sitemaps/SitemapRegistry.php b/app/Services/Sitemaps/SitemapRegistry.php index a8a7a89f..2be80bde 100644 --- a/app/Services/Sitemaps/SitemapRegistry.php +++ b/app/Services/Sitemaps/SitemapRegistry.php @@ -6,6 +6,7 @@ namespace App\Services\Sitemaps; use App\Services\Sitemaps\Builders\ArtworksSitemapBuilder; use App\Services\Sitemaps\Builders\AcademyChallengesSitemapBuilder; +use App\Services\Sitemaps\Builders\AcademyCoursesSitemapBuilder; use App\Services\Sitemaps\Builders\AcademyLessonsSitemapBuilder; use App\Services\Sitemaps\Builders\AcademyPacksSitemapBuilder; use App\Services\Sitemaps\Builders\AcademyPromptsSitemapBuilder; @@ -31,6 +32,7 @@ final class SitemapRegistry public function __construct( ArtworksSitemapBuilder $artworks, + AcademyCoursesSitemapBuilder $academyCourses, AcademyLessonsSitemapBuilder $academyLessons, AcademyPromptsSitemapBuilder $academyPrompts, AcademyPacksSitemapBuilder $academyPacks, @@ -50,6 +52,7 @@ final class SitemapRegistry ) { $this->builders = [ $artworks->name() => $artworks, + $academyCourses->name() => $academyCourses, $academyLessons->name() => $academyLessons, $academyPrompts->name() => $academyPrompts, $academyPacks->name() => $academyPacks, diff --git a/bootstrap/ssr/assets/ArtworkShareModal-BI8kkaqs.js b/bootstrap/ssr/assets/ArtworkShareModal-BPM8yel5.js similarity index 99% rename from bootstrap/ssr/assets/ArtworkShareModal-BI8kkaqs.js rename to bootstrap/ssr/assets/ArtworkShareModal-BPM8yel5.js index 9b5f2555..1789116e 100644 --- a/bootstrap/ssr/assets/ArtworkShareModal-BI8kkaqs.js +++ b/bootstrap/ssr/assets/ArtworkShareModal-BPM8yel5.js @@ -18,7 +18,7 @@ import "./vendor-tooltip-CIQaDNlG.js"; import "node:process"; import "node:path"; import "node:url"; -import "./vendor-realtime-DYEIbD6w.js"; +import "./vendor-realtime-Koiu-_pw.js"; import "buffer"; import "child_process"; import "net"; diff --git a/bootstrap/ssr/assets/turndown.es-8lfE8z0s.js b/bootstrap/ssr/assets/turndown.es-8lfE8z0s.js new file mode 100644 index 00000000..500f98b3 --- /dev/null +++ b/bootstrap/ssr/assets/turndown.es-8lfE8z0s.js @@ -0,0 +1,640 @@ +function extend(destination) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key]; + } + } + return destination; +} +function repeat(character, count) { + return Array(count + 1).join(character); +} +function trimLeadingNewlines(string) { + return string.replace(/^\n*/, ""); +} +function trimTrailingNewlines(string) { + var indexEnd = string.length; + while (indexEnd > 0 && string[indexEnd - 1] === "\n") indexEnd--; + return string.substring(0, indexEnd); +} +function trimNewlines(string) { + return trimTrailingNewlines(trimLeadingNewlines(string)); +} +var blockElements = ["ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "TABLE", "TBODY", "TD", "TFOOT", "TH", "THEAD", "TR", "UL"]; +function isBlock(node) { + return is(node, blockElements); +} +var voidElements = ["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "META", "PARAM", "SOURCE", "TRACK", "WBR"]; +function isVoid(node) { + return is(node, voidElements); +} +function hasVoid(node) { + return has(node, voidElements); +} +var meaningfulWhenBlankElements = ["A", "TABLE", "THEAD", "TBODY", "TFOOT", "TH", "TD", "IFRAME", "SCRIPT", "AUDIO", "VIDEO"]; +function isMeaningfulWhenBlank(node) { + return is(node, meaningfulWhenBlankElements); +} +function hasMeaningfulWhenBlank(node) { + return has(node, meaningfulWhenBlankElements); +} +function is(node, tagNames) { + return tagNames.indexOf(node.nodeName) >= 0; +} +function has(node, tagNames) { + return node.getElementsByTagName && tagNames.some(function(tagName) { + return node.getElementsByTagName(tagName).length; + }); +} +var markdownEscapes = [[/\\/g, "\\\\"], [/\*/g, "\\*"], [/^-/g, "\\-"], [/^\+ /g, "\\+ "], [/^(=+)/g, "\\$1"], [/^(#{1,6}) /g, "\\$1 "], [/`/g, "\\`"], [/^~~~/g, "\\~~~"], [/\[/g, "\\["], [/\]/g, "\\]"], [/^>/g, "\\>"], [/_/g, "\\_"], [/^(\d+)\. /g, "$1\\. "]]; +function escapeMarkdown(string) { + return markdownEscapes.reduce(function(accumulator, escape) { + return accumulator.replace(escape[0], escape[1]); + }, string); +} +var rules = {}; +rules.paragraph = { + filter: "p", + replacement: function(content) { + return "\n\n" + content + "\n\n"; + } +}; +rules.lineBreak = { + filter: "br", + replacement: function(content, node, options) { + return options.br + "\n"; + } +}; +rules.heading = { + filter: ["h1", "h2", "h3", "h4", "h5", "h6"], + replacement: function(content, node, options) { + var hLevel = Number(node.nodeName.charAt(1)); + if (options.headingStyle === "setext" && hLevel < 3) { + var underline = repeat(hLevel === 1 ? "=" : "-", content.length); + return "\n\n" + content + "\n" + underline + "\n\n"; + } else { + return "\n\n" + repeat("#", hLevel) + " " + content + "\n\n"; + } + } +}; +rules.blockquote = { + filter: "blockquote", + replacement: function(content) { + content = trimNewlines(content).replace(/^/gm, "> "); + return "\n\n" + content + "\n\n"; + } +}; +rules.list = { + filter: ["ul", "ol"], + replacement: function(content, node) { + var parent = node.parentNode; + if (parent.nodeName === "LI" && parent.lastElementChild === node) { + return "\n" + content; + } else { + return "\n\n" + content + "\n\n"; + } + } +}; +rules.listItem = { + filter: "li", + replacement: function(content, node, options) { + var prefix = options.bulletListMarker + " "; + var parent = node.parentNode; + if (parent.nodeName === "OL") { + var start = parent.getAttribute("start"); + var index = Array.prototype.indexOf.call(parent.children, node); + prefix = (start ? Number(start) + index : index + 1) + ". "; + } + var isParagraph = /\n$/.test(content); + content = trimNewlines(content) + (isParagraph ? "\n" : ""); + content = content.replace(/\n/gm, "\n" + " ".repeat(prefix.length)); + return prefix + content + (node.nextSibling ? "\n" : ""); + } +}; +rules.indentedCodeBlock = { + filter: function(node, options) { + return options.codeBlockStyle === "indented" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE"; + }, + replacement: function(content, node, options) { + return "\n\n " + node.firstChild.textContent.replace(/\n/g, "\n ") + "\n\n"; + } +}; +rules.fencedCodeBlock = { + filter: function(node, options) { + return options.codeBlockStyle === "fenced" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE"; + }, + replacement: function(content, node, options) { + var className = node.firstChild.getAttribute("class") || ""; + var language = (className.match(/language-(\S+)/) || [null, ""])[1]; + var code = node.firstChild.textContent; + var fenceChar = options.fence.charAt(0); + var fenceSize = 3; + var fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm"); + var match; + while (match = fenceInCodeRegex.exec(code)) { + if (match[0].length >= fenceSize) { + fenceSize = match[0].length + 1; + } + } + var fence = repeat(fenceChar, fenceSize); + return "\n\n" + fence + language + "\n" + code.replace(/\n$/, "") + "\n" + fence + "\n\n"; + } +}; +rules.horizontalRule = { + filter: "hr", + replacement: function(content, node, options) { + return "\n\n" + options.hr + "\n\n"; + } +}; +rules.inlineLink = { + filter: function(node, options) { + return options.linkStyle === "inlined" && node.nodeName === "A" && node.getAttribute("href"); + }, + replacement: function(content, node) { + var href = escapeLinkDestination(node.getAttribute("href")); + var title = escapeLinkTitle(cleanAttribute(node.getAttribute("title"))); + var titlePart = title ? ' "' + title + '"' : ""; + return "[" + content + "](" + href + titlePart + ")"; + } +}; +rules.referenceLink = { + filter: function(node, options) { + return options.linkStyle === "referenced" && node.nodeName === "A" && node.getAttribute("href"); + }, + replacement: function(content, node, options) { + var href = escapeLinkDestination(node.getAttribute("href")); + var title = cleanAttribute(node.getAttribute("title")); + if (title) title = ' "' + escapeLinkTitle(title) + '"'; + var replacement; + var reference; + switch (options.linkReferenceStyle) { + case "collapsed": + replacement = "[" + content + "][]"; + reference = "[" + content + "]: " + href + title; + break; + case "shortcut": + replacement = "[" + content + "]"; + reference = "[" + content + "]: " + href + title; + break; + default: + var id = this.references.length + 1; + replacement = "[" + content + "][" + id + "]"; + reference = "[" + id + "]: " + href + title; + } + this.references.push(reference); + return replacement; + }, + references: [], + append: function(options) { + var references = ""; + if (this.references.length) { + references = "\n\n" + this.references.join("\n") + "\n\n"; + this.references = []; + } + return references; + } +}; +rules.emphasis = { + filter: ["em", "i"], + replacement: function(content, node, options) { + if (!content.trim()) return ""; + return options.emDelimiter + content + options.emDelimiter; + } +}; +rules.strong = { + filter: ["strong", "b"], + replacement: function(content, node, options) { + if (!content.trim()) return ""; + return options.strongDelimiter + content + options.strongDelimiter; + } +}; +rules.code = { + filter: function(node) { + var hasSiblings = node.previousSibling || node.nextSibling; + var isCodeBlock = node.parentNode.nodeName === "PRE" && !hasSiblings; + return node.nodeName === "CODE" && !isCodeBlock; + }, + replacement: function(content) { + if (!content) return ""; + content = content.replace(/\r?\n|\r/g, " "); + var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? " " : ""; + var delimiter = "`"; + var matches = content.match(/`+/gm) || []; + while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + "`"; + return delimiter + extraSpace + content + extraSpace + delimiter; + } +}; +rules.image = { + filter: "img", + replacement: function(content, node) { + var alt = escapeMarkdown(cleanAttribute(node.getAttribute("alt"))); + var src = escapeLinkDestination(node.getAttribute("src") || ""); + var title = cleanAttribute(node.getAttribute("title")); + var titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : ""; + return src ? "![" + alt + "](" + src + titlePart + ")" : ""; + } +}; +function cleanAttribute(attribute) { + return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : ""; +} +function escapeLinkDestination(destination) { + var escaped = destination.replace(/([<>()])/g, "\\$1"); + return escaped.indexOf(" ") >= 0 ? "<" + escaped + ">" : escaped; +} +function escapeLinkTitle(title) { + return title.replace(/"/g, '\\"'); +} +function Rules(options) { + this.options = options; + this._keep = []; + this._remove = []; + this.blankRule = { + replacement: options.blankReplacement + }; + this.keepReplacement = options.keepReplacement; + this.defaultRule = { + replacement: options.defaultReplacement + }; + this.array = []; + for (var key in options.rules) this.array.push(options.rules[key]); +} +Rules.prototype = { + add: function(key, rule) { + this.array.unshift(rule); + }, + keep: function(filter) { + this._keep.unshift({ + filter, + replacement: this.keepReplacement + }); + }, + remove: function(filter) { + this._remove.unshift({ + filter, + replacement: function() { + return ""; + } + }); + }, + forNode: function(node) { + if (node.isBlank) return this.blankRule; + var rule; + if (rule = findRule(this.array, node, this.options)) return rule; + if (rule = findRule(this._keep, node, this.options)) return rule; + if (rule = findRule(this._remove, node, this.options)) return rule; + return this.defaultRule; + }, + forEach: function(fn) { + for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); + } +}; +function findRule(rules2, node, options) { + for (var i = 0; i < rules2.length; i++) { + var rule = rules2[i]; + if (filterValue(rule, node, options)) return rule; + } + return void 0; +} +function filterValue(rule, node, options) { + var filter = rule.filter; + if (typeof filter === "string") { + if (filter === node.nodeName.toLowerCase()) return true; + } else if (Array.isArray(filter)) { + if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true; + } else if (typeof filter === "function") { + if (filter.call(rule, node, options)) return true; + } else { + throw new TypeError("`filter` needs to be a string, array, or function"); + } +} +function collapseWhitespace(options) { + var element = options.element; + var isBlock2 = options.isBlock; + var isVoid2 = options.isVoid; + var isPre = options.isPre || function(node2) { + return node2.nodeName === "PRE"; + }; + if (!element.firstChild || isPre(element)) return; + var prevText = null; + var keepLeadingWs = false; + var prev = null; + var node = next(prev, element, isPre); + while (node !== element) { + if (node.nodeType === 3 || node.nodeType === 4) { + var text = node.data.replace(/[ \r\n\t]+/g, " "); + if ((!prevText || / $/.test(prevText.data)) && !keepLeadingWs && text[0] === " ") { + text = text.substr(1); + } + if (!text) { + node = remove(node); + continue; + } + node.data = text; + prevText = node; + } else if (node.nodeType === 1) { + if (isBlock2(node) || node.nodeName === "BR") { + if (prevText) { + prevText.data = prevText.data.replace(/ $/, ""); + } + prevText = null; + keepLeadingWs = false; + } else if (isVoid2(node) || isPre(node)) { + prevText = null; + keepLeadingWs = true; + } else if (prevText) { + keepLeadingWs = false; + } + } else { + node = remove(node); + continue; + } + var nextNode = next(prev, node, isPre); + prev = node; + node = nextNode; + } + if (prevText) { + prevText.data = prevText.data.replace(/ $/, ""); + if (!prevText.data) { + remove(prevText); + } + } +} +function remove(node) { + var next2 = node.nextSibling || node.parentNode; + node.parentNode.removeChild(node); + return next2; +} +function next(prev, current, isPre) { + if (prev && prev.parentNode === current || isPre(current)) { + return current.nextSibling || current.parentNode; + } + return current.firstChild || current.nextSibling || current.parentNode; +} +var root = typeof window !== "undefined" ? window : {}; +function canParseHTMLNatively() { + var Parser = root.DOMParser; + var canParse = false; + try { + if (new Parser().parseFromString("", "text/html")) { + canParse = true; + } + } catch (e) { + } + return canParse; +} +function createHTMLParser() { + var Parser = function() { + }; + { + var domino = require("@mixmark-io/domino"); + Parser.prototype.parseFromString = function(string) { + return domino.createDocument(string); + }; + } + return Parser; +} +var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser(); +function RootNode(input, options) { + var root2; + if (typeof input === "string") { + var doc = htmlParser().parseFromString( + // DOM parsers arrange elements in the and . + // Wrapping in a custom element ensures elements are reliably arranged in + // a single element. + '' + input + "", + "text/html" + ); + root2 = doc.getElementById("turndown-root"); + } else { + root2 = input.cloneNode(true); + } + collapseWhitespace({ + element: root2, + isBlock, + isVoid, + isPre: options.preformattedCode ? isPreOrCode : null + }); + return root2; +} +var _htmlParser; +function htmlParser() { + _htmlParser = _htmlParser || new HTMLParser(); + return _htmlParser; +} +function isPreOrCode(node) { + return node.nodeName === "PRE" || node.nodeName === "CODE"; +} +function Node(node, options) { + node.isBlock = isBlock(node); + node.isCode = node.nodeName === "CODE" || node.parentNode.isCode; + node.isBlank = isBlank(node); + node.flankingWhitespace = flankingWhitespace(node, options); + return node; +} +function isBlank(node) { + return !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node); +} +function flankingWhitespace(node, options) { + if (node.isBlock || options.preformattedCode && node.isCode) { + return { + leading: "", + trailing: "" + }; + } + var edges = edgeWhitespace(node.textContent); + if (edges.leadingAscii && isFlankedByWhitespace("left", node, options)) { + edges.leading = edges.leadingNonAscii; + } + if (edges.trailingAscii && isFlankedByWhitespace("right", node, options)) { + edges.trailing = edges.trailingNonAscii; + } + return { + leading: edges.leading, + trailing: edges.trailing + }; +} +function edgeWhitespace(string) { + var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/); + return { + leading: m[1], + // whole string for whitespace-only strings + leadingAscii: m[2], + leadingNonAscii: m[3], + trailing: m[4], + // empty for whitespace-only strings + trailingNonAscii: m[5], + trailingAscii: m[6] + }; +} +function isFlankedByWhitespace(side, node, options) { + var sibling; + var regExp; + var isFlanked; + if (side === "left") { + sibling = node.previousSibling; + regExp = / $/; + } else { + sibling = node.nextSibling; + regExp = /^ /; + } + if (sibling) { + if (sibling.nodeType === 3) { + isFlanked = regExp.test(sibling.nodeValue); + } else if (options.preformattedCode && sibling.nodeName === "CODE") { + isFlanked = false; + } else if (sibling.nodeType === 1 && !isBlock(sibling)) { + isFlanked = regExp.test(sibling.textContent); + } + } + return isFlanked; +} +var reduce = Array.prototype.reduce; +function TurndownService(options) { + if (!(this instanceof TurndownService)) return new TurndownService(options); + var defaults = { + rules, + headingStyle: "setext", + hr: "* * *", + bulletListMarker: "*", + codeBlockStyle: "indented", + fence: "```", + emDelimiter: "_", + strongDelimiter: "**", + linkStyle: "inlined", + linkReferenceStyle: "full", + br: " ", + preformattedCode: false, + blankReplacement: function(content, node) { + return node.isBlock ? "\n\n" : ""; + }, + keepReplacement: function(content, node) { + return node.isBlock ? "\n\n" + node.outerHTML + "\n\n" : node.outerHTML; + }, + defaultReplacement: function(content, node) { + return node.isBlock ? "\n\n" + content + "\n\n" : content; + } + }; + this.options = extend({}, defaults, options); + this.rules = new Rules(this.options); +} +TurndownService.prototype = { + /** + * The entry point for converting a string or DOM node to Markdown + * @public + * @param {String|HTMLElement} input The string or DOM node to convert + * @returns A Markdown representation of the input + * @type String + */ + turndown: function(input) { + if (!canConvert(input)) { + throw new TypeError(input + " is not a string, or an element/document/fragment node."); + } + if (input === "") return ""; + var output = process.call(this, new RootNode(input, this.options)); + return postProcess.call(this, output); + }, + /** + * Add one or more plugins + * @public + * @param {Function|Array} plugin The plugin or array of plugins to add + * @returns The Turndown instance for chaining + * @type Object + */ + use: function(plugin) { + if (Array.isArray(plugin)) { + for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); + } else if (typeof plugin === "function") { + plugin(this); + } else { + throw new TypeError("plugin must be a Function or an Array of Functions"); + } + return this; + }, + /** + * Adds a rule + * @public + * @param {String} key The unique key of the rule + * @param {Object} rule The rule + * @returns The Turndown instance for chaining + * @type Object + */ + addRule: function(key, rule) { + this.rules.add(key, rule); + return this; + }, + /** + * Keep a node (as HTML) that matches the filter + * @public + * @param {String|Array|Function} filter The unique key of the rule + * @returns The Turndown instance for chaining + * @type Object + */ + keep: function(filter) { + this.rules.keep(filter); + return this; + }, + /** + * Remove a node that matches the filter + * @public + * @param {String|Array|Function} filter The unique key of the rule + * @returns The Turndown instance for chaining + * @type Object + */ + remove: function(filter) { + this.rules.remove(filter); + return this; + }, + /** + * Escapes Markdown syntax + * @public + * @param {String} string The string to escape + * @returns A string with Markdown syntax escaped + * @type String + */ + escape: function(string) { + return escapeMarkdown(string); + } +}; +function process(parentNode) { + var self = this; + return reduce.call(parentNode.childNodes, function(output, node) { + node = new Node(node, self.options); + var replacement = ""; + if (node.nodeType === 3) { + replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); + } else if (node.nodeType === 1) { + replacement = replacementForNode.call(self, node); + } + return join(output, replacement); + }, ""); +} +function postProcess(output) { + var self = this; + this.rules.forEach(function(rule) { + if (typeof rule.append === "function") { + output = join(output, rule.append(self.options)); + } + }); + return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, ""); +} +function replacementForNode(node) { + var rule = this.rules.forNode(node); + var content = process.call(this, node); + var whitespace = node.flankingWhitespace; + if (whitespace.leading || whitespace.trailing) content = content.trim(); + return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing; +} +function join(output, replacement) { + var s1 = trimTrailingNewlines(output); + var s2 = trimLeadingNewlines(replacement); + var nls = Math.max(output.length - s1.length, replacement.length - s2.length); + var separator = "\n\n".substring(0, nls); + return s1 + separator + s2; +} +function canConvert(input) { + return input != null && (typeof input === "string" || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11)); +} +export { + TurndownService as default +}; diff --git a/bootstrap/ssr/assets/vendor-realtime-DYEIbD6w.js b/bootstrap/ssr/assets/vendor-realtime-Koiu-_pw.js similarity index 100% rename from bootstrap/ssr/assets/vendor-realtime-DYEIbD6w.js rename to bootstrap/ssr/assets/vendor-realtime-Koiu-_pw.js index ae798d41..79b999f8 100644 --- a/bootstrap/ssr/assets/vendor-realtime-DYEIbD6w.js +++ b/bootstrap/ssr/assets/vendor-realtime-Koiu-_pw.js @@ -1,17 +1,17 @@ import require$$0 from "util"; import stream from "stream"; +import require$$4 from "https"; import require$$5 from "url"; import require$$6 from "fs"; import require$$1 from "crypto"; import require$$4$2 from "assert"; import require$$1$1 from "buffer"; import require$$2 from "child_process"; +import require$$4$1 from "events"; import require$$8 from "net"; import require$$10 from "tls"; import { c as commonjsGlobal, g as getDefaultExportFromCjs } from "./vendor-tiptap-DRFaxGEb.js"; -import require$$4$1 from "events"; import require$$3 from "http"; -import require$$4 from "https"; class u { constructor() { this.notificationCreatedEvent = ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"; diff --git a/bootstrap/ssr/ssr-manifest.json b/bootstrap/ssr/ssr-manifest.json index 532f7741..36cfea31 100644 --- a/bootstrap/ssr/ssr-manifest.json +++ b/bootstrap/ssr/ssr-manifest.json @@ -14,10 +14,10 @@ "\u0000D:/Sites/Skinbase26/node_modules/nprogress/nprogress.js?commonjs-es-import": [], "\u0000D:/Sites/Skinbase26/node_modules/nprogress/nprogress.js?commonjs-module": [], "\u0000D:/Sites/Skinbase26/node_modules/pusher-js/dist/node/pusher.js?commonjs-es-import": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000D:/Sites/Skinbase26/node_modules/pusher-js/dist/node/pusher.js?commonjs-module": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000D:/Sites/Skinbase26/node_modules/qs/lib/index.js?commonjs-es-import": [], "\u0000D:/Sites/Skinbase26/node_modules/react-dom/cjs/react-dom-client.development.js?commonjs-exports": [], @@ -97,46 +97,46 @@ "/build/assets/vendor-tiptap-DRFaxGEb.js" ], "\u0000assert?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000buffer?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000child_process?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000commonjsHelpers.js": [ "/build/assets/vendor-tiptap-DRFaxGEb.js" ], "\u0000crypto?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000events?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000fs?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000http?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000https?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000net?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000stream?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000tls?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000url?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "\u0000util?commonjs-external": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "node_modules/@emoji-mart/data/sets/15/native.json": [ "/build/assets/emoji-data-4xGXbtDn.js" @@ -1035,12 +1035,13 @@ "node_modules/inline-style-parser/cjs/index.js": [], "node_modules/is-plain-obj/index.js": [], "node_modules/laravel-echo/dist/echo.js": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "node_modules/linkifyjs/dist/linkify.mjs": [ "/build/assets/vendor-tiptap-DRFaxGEb.js" ], "node_modules/lodash.isequal/index.js": [], + "node_modules/marked/lib/marked.esm.js": [], "node_modules/math-intrinsics/abs.js": [], "node_modules/math-intrinsics/floor.js": [], "node_modules/math-intrinsics/isNaN.js": [], @@ -1934,7 +1935,7 @@ ], "node_modules/proxy-from-env/index.js": [], "node_modules/pusher-js/dist/node/pusher.js": [ - "/build/assets/vendor-realtime-DYEIbD6w.js" + "/build/assets/vendor-realtime-Koiu-_pw.js" ], "node_modules/qs/lib/formats.js": [], "node_modules/qs/lib/index.js": [], @@ -1999,6 +2000,9 @@ ], "node_modules/trim-lines/index.js": [], "node_modules/trough/lib/index.js": [], + "node_modules/turndown/lib/turndown.es.js": [ + "/build/assets/turndown.es-8lfE8z0s.js" + ], "node_modules/unified/lib/callable-instance.js": [], "node_modules/unified/lib/index.js": [], "node_modules/unist-util-is/lib/index.js": [], @@ -2035,10 +2039,14 @@ "resources/js/Layouts/SettingsLayout.jsx": [], "resources/js/Layouts/StudioLayout.jsx": [], "resources/js/Pages/Academy/ChallengeSubmit.jsx": [], + "resources/js/Pages/Academy/CoursesIndex.jsx": [], + "resources/js/Pages/Academy/CoursesShow.jsx": [], "resources/js/Pages/Academy/Index.jsx": [], "resources/js/Pages/Academy/List.jsx": [], "resources/js/Pages/Academy/Pricing.jsx": [], "resources/js/Pages/Academy/Show.jsx": [], + "resources/js/Pages/Admin/Academy/CourseBuilder.jsx": [], + "resources/js/Pages/Admin/Academy/CourseEditor.jsx": [], "resources/js/Pages/Admin/Academy/CrudForm.jsx": [], "resources/js/Pages/Admin/Academy/CrudIndex.jsx": [], "resources/js/Pages/Admin/Academy/Dashboard.jsx": [], @@ -2125,6 +2133,7 @@ "resources/js/Pages/Moderation/AiBiographyAdmin.jsx": [], "resources/js/Pages/Moderation/ArtworkMaturityQueue.jsx": [], "resources/js/Pages/News/NewsComments.jsx": [], + "resources/js/Pages/News/NewsImagePreview.jsx": [], "resources/js/Pages/Profile/ProfileGallery.jsx": [], "resources/js/Pages/Profile/ProfileShow.jsx": [], "resources/js/Pages/Settings/ProfileEdit.jsx": [], @@ -2223,7 +2232,7 @@ "resources/js/components/artwork/ArtworkRecommendationsRails.jsx": [], "resources/js/components/artwork/ArtworkShareButton.jsx": [], "resources/js/components/artwork/ArtworkShareModal.jsx": [ - "/build/assets/ArtworkShareModal-BI8kkaqs.js" + "/build/assets/ArtworkShareModal-BPM8yel5.js" ], "resources/js/components/artwork/ArtworkTags.jsx": [], "resources/js/components/artwork/AuthorBioPopover.jsx": [], diff --git a/bootstrap/ssr/ssr.js b/bootstrap/ssr/ssr.js index c1ea899b..38974871 100644 --- a/bootstrap/ssr/ssr.js +++ b/bootstrap/ssr/ssr.js @@ -17,7 +17,7 @@ import { t as tippy } from "./assets/vendor-tooltip-CIQaDNlG.js"; import minproc from "node:process"; import minpath from "node:path"; import { fileURLToPath } from "node:url"; -import { P as Pusher, E as E$1 } from "./assets/vendor-realtime-DYEIbD6w.js"; +import { P as Pusher, E as E$2 } from "./assets/vendor-realtime-Koiu-_pw.js"; import { u as useReducedMotion, m as motion, A as AnimatePresence } from "./assets/vendor-motion-CotXNotG.js"; import * as s from "process"; import require$$2 from "async_hooks"; @@ -95,8 +95,8 @@ function getGlobal() { if (typeof global !== "undefined") return global; return {}; } -const G$1 = getGlobal(); -const FormDataCtor = typeof G$1.FormData !== "undefined" ? G$1.FormData : void 0; +const G$2 = getGlobal(); +const FormDataCtor = typeof G$2.FormData !== "undefined" ? G$2.FormData : void 0; const isFormData = (thing) => { let kind; return thing && (FormDataCtor && thing instanceof FormDataCtor || isFunction$1(thing.append) && ((kind = kindOf(thing)) === "formdata" || // detect form-data instance @@ -117,12 +117,12 @@ function forEach(obj, fn, { allOwnKeys = false } = {}) { return; } let i; - let l; + let l3; if (typeof obj !== "object") { obj = [obj]; } if (isArray(obj)) { - for (i = 0, l = obj.length; i < l; i++) { + for (i = 0, l3 = obj.length; i < l3; i++) { fn.call(null, obj[i], i, obj); } } else { @@ -177,7 +177,7 @@ function merge$1() { result[targetKey] = val; } }; - for (let i = 0, l = arguments.length; i < l; i++) { + for (let i = 0, l3 = arguments.length; i < l3; i++) { arguments[i] && forEach(arguments[i], assignValue); } return result; @@ -290,7 +290,7 @@ const matchAll = (regExp, str) => { }; const isHTMLForm = kindOfTest("HTMLFormElement"); const toCamelCase = (str) => { - return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) { + return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m2, p1, p2) { return p1.toUpperCase() + p2; }); }; @@ -3669,15 +3669,15 @@ function requireImplementation() { for (var i = 0; i < a.length; i += 1) { arr[i] = a[i]; } - for (var j = 0; j < b2.length; j += 1) { - arr[j + a.length] = b2[j]; + for (var j2 = 0; j2 < b2.length; j2 += 1) { + arr[j2 + a.length] = b2[j2]; } return arr; }; var slicy = function slicy2(arrLike, offset) { var arr = []; - for (var i = offset, j = 0; i < arrLike.length; i += 1, j += 1) { - arr[j] = arrLike[i]; + for (var i = offset, j2 = 0; i < arrLike.length; i += 1, j2 += 1) { + arr[j2] = arrLike[i]; } return arr; }; @@ -3832,15 +3832,15 @@ function requireGetProto() { var reflectGetProto = requireReflect_getPrototypeOf(); var originalGetProto = requireObject_getPrototypeOf(); var getDunderProto = /* @__PURE__ */ requireGet(); - getProto = reflectGetProto ? function getProto2(O) { - return reflectGetProto(O); - } : originalGetProto ? function getProto2(O) { - if (!O || typeof O !== "object" && typeof O !== "function") { + getProto = reflectGetProto ? function getProto2(O2) { + return reflectGetProto(O2); + } : originalGetProto ? function getProto2(O2) { + if (!O2 || typeof O2 !== "object" && typeof O2 !== "function") { throw new TypeError("getProto: not an object"); } - return originalGetProto(O); - } : getDunderProto ? function getProto2(O) { - return getDunderProto(O); + return originalGetProto(O2); + } : getDunderProto ? function getProto2(O2) { + return getDunderProto(O2); } : null; return getProto; } @@ -5435,11 +5435,11 @@ function requireMs() { if (hasRequiredMs) return ms; hasRequiredMs = 1; var s2 = 1e3; - var m = s2 * 60; - var h = m * 60; + var m2 = s2 * 60; + var h = m2 * 60; var d2 = h * 24; var w2 = d2 * 7; - var y = d2 * 365.25; + var y2 = d2 * 365.25; ms = function(val, options) { options = options || {}; var type2 = typeof val; @@ -5471,7 +5471,7 @@ function requireMs() { case "yrs": case "yr": case "y": - return n * y; + return n * y2; case "weeks": case "week": case "w": @@ -5491,7 +5491,7 @@ function requireMs() { case "mins": case "min": case "m": - return n * m; + return n * m2; case "seconds": case "second": case "secs": @@ -5516,8 +5516,8 @@ function requireMs() { if (msAbs >= h) { return Math.round(ms2 / h) + "h"; } - if (msAbs >= m) { - return Math.round(ms2 / m) + "m"; + if (msAbs >= m2) { + return Math.round(ms2 / m2) + "m"; } if (msAbs >= s2) { return Math.round(ms2 / s2) + "s"; @@ -5532,8 +5532,8 @@ function requireMs() { if (msAbs >= h) { return plural(ms2, msAbs, h, "hour"); } - if (msAbs >= m) { - return plural(ms2, msAbs, m, "minute"); + if (msAbs >= m2) { + return plural(ms2, msAbs, m2, "minute"); } if (msAbs >= s2) { return plural(ms2, msAbs, s2, "second"); @@ -5632,8 +5632,8 @@ function requireCommon() { } return enabledCache; }, - set: (v) => { - enableOverride = v; + set: (v2) => { + enableOverride = v2; } }); if (typeof createDebug.init === "function") { @@ -5828,11 +5828,11 @@ function requireBrowser() { if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { return false; } - let m; + let m2; return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773 typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31? // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker + typeof navigator !== "undefined" && navigator.userAgent && (m2 = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m2[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); } function formatArgs(args) { @@ -5886,9 +5886,9 @@ function requireBrowser() { } module.exports = requireCommon()(exports$1); const { formatters } = module.exports; - formatters.j = function(v) { + formatters.j = function(v2) { try { - return JSON.stringify(v); + return JSON.stringify(v2); } catch (error) { return "[UnexpectedJSONParseError]: " + error.message; } @@ -6189,13 +6189,13 @@ function requireNode() { } module.exports = requireCommon()(exports$1); const { formatters } = module.exports; - formatters.o = function(v) { + formatters.o = function(v2) { this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" "); + return util.inspect(v2, this.inspectOpts).split("\n").map((str) => str.trim()).join(" "); }; - formatters.O = function(v) { + formatters.O = function(v2) { this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); + return util.inspect(v2, this.inspectOpts); }; })(node$1, node$1.exports); return node$1.exports; @@ -7129,9 +7129,9 @@ function estimateDataURLDecodedBytes(url) { } let pad2 = 0; let idx = len - 1; - const tailIsPct3D = (j) => j >= 2 && body2.charCodeAt(j - 2) === 37 && // '%' - body2.charCodeAt(j - 1) === 51 && // '3' - (body2.charCodeAt(j) === 68 || body2.charCodeAt(j) === 100); + const tailIsPct3D = (j2) => j2 >= 2 && body2.charCodeAt(j2 - 2) === 37 && // '%' + body2.charCodeAt(j2 - 1) === 51 && // '3' + (body2.charCodeAt(j2) === 68 || body2.charCodeAt(j2) === 100); if (idx >= 0) { if (body2.charCodeAt(idx) === 61) { pad2++; @@ -9156,7 +9156,7 @@ function requireCjs$3() { return cjs$3; } var cjsExports$1 = requireCjs$3(); -const oe = /* @__PURE__ */ getDefaultExportFromCjs(cjsExports$1); +const oe$1 = /* @__PURE__ */ getDefaultExportFromCjs(cjsExports$1); var util_inspect; var hasRequiredUtil_inspect; function requireUtil_inspect() { @@ -9203,8 +9203,8 @@ function requireObjectInspect() { var hasShammedSymbols = typeof Symbol === "function" && typeof Symbol.iterator === "object"; var toStringTag2 = typeof Symbol === "function" && Symbol.toStringTag && (typeof Symbol.toStringTag === hasShammedSymbols ? "object" : "symbol") ? Symbol.toStringTag : null; var isEnumerable = Object.prototype.propertyIsEnumerable; - var gPO = (typeof Reflect === "function" ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ([].__proto__ === Array.prototype ? function(O) { - return O.__proto__; + var gPO = (typeof Reflect === "function" ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ([].__proto__ === Array.prototype ? function(O2) { + return O2.__proto__; } : null); function addNumericSeparator(num, str) { if (num === Infinity || num === -Infinity || num !== num || num && num > -1e3 && num < 1e3 || $test.call(/e/, str)) { @@ -9489,106 +9489,106 @@ function requireObjectInspect() { if (f2.name) { return f2.name; } - var m = $match.call(functionToString.call(f2), /^function\s*([\w$]+)/); - if (m) { - return m[1]; + var m2 = $match.call(functionToString.call(f2), /^function\s*([\w$]+)/); + if (m2) { + return m2[1]; } return null; } - function indexOf(xs, x) { + function indexOf(xs, x2) { if (xs.indexOf) { - return xs.indexOf(x); + return xs.indexOf(x2); } - for (var i = 0, l = xs.length; i < l; i++) { - if (xs[i] === x) { + for (var i = 0, l3 = xs.length; i < l3; i++) { + if (xs[i] === x2) { return i; } } return -1; } - function isMap(x) { - if (!mapSize || !x || typeof x !== "object") { + function isMap(x2) { + if (!mapSize || !x2 || typeof x2 !== "object") { return false; } try { - mapSize.call(x); + mapSize.call(x2); try { - setSize.call(x); + setSize.call(x2); } catch (s2) { return true; } - return x instanceof Map; + return x2 instanceof Map; } catch (e) { } return false; } - function isWeakMap(x) { - if (!weakMapHas || !x || typeof x !== "object") { + function isWeakMap(x2) { + if (!weakMapHas || !x2 || typeof x2 !== "object") { return false; } try { - weakMapHas.call(x, weakMapHas); + weakMapHas.call(x2, weakMapHas); try { - weakSetHas.call(x, weakSetHas); + weakSetHas.call(x2, weakSetHas); } catch (s2) { return true; } - return x instanceof WeakMap; + return x2 instanceof WeakMap; } catch (e) { } return false; } - function isWeakRef(x) { - if (!weakRefDeref || !x || typeof x !== "object") { + function isWeakRef(x2) { + if (!weakRefDeref || !x2 || typeof x2 !== "object") { return false; } try { - weakRefDeref.call(x); + weakRefDeref.call(x2); return true; } catch (e) { } return false; } - function isSet(x) { - if (!setSize || !x || typeof x !== "object") { + function isSet(x2) { + if (!setSize || !x2 || typeof x2 !== "object") { return false; } try { - setSize.call(x); + setSize.call(x2); try { - mapSize.call(x); - } catch (m) { + mapSize.call(x2); + } catch (m2) { return true; } - return x instanceof Set; + return x2 instanceof Set; } catch (e) { } return false; } - function isWeakSet(x) { - if (!weakSetHas || !x || typeof x !== "object") { + function isWeakSet(x2) { + if (!weakSetHas || !x2 || typeof x2 !== "object") { return false; } try { - weakSetHas.call(x, weakSetHas); + weakSetHas.call(x2, weakSetHas); try { - weakMapHas.call(x, weakMapHas); + weakMapHas.call(x2, weakMapHas); } catch (s2) { return true; } - return x instanceof WeakSet; + return x2 instanceof WeakSet; } catch (e) { } return false; } - function isElement(x) { - if (!x || typeof x !== "object") { + function isElement(x2) { + if (!x2 || typeof x2 !== "object") { return false; } - if (typeof HTMLElement !== "undefined" && x instanceof HTMLElement) { + if (typeof HTMLElement !== "undefined" && x2 instanceof HTMLElement) { return true; } - return typeof x.nodeName === "string" && typeof x.getAttribute === "function"; + return typeof x2.nodeName === "string" && typeof x2.getAttribute === "function"; } function inspectString(str, opts) { if (str.length > opts.maxStringLength) { @@ -9603,15 +9603,15 @@ function requireObjectInspect() { } function lowbyte(c) { var n = c.charCodeAt(0); - var x = { + var x2 = { 8: "b", 9: "t", 10: "n", 12: "f", 13: "r" }[n]; - if (x) { - return "\\" + x; + if (x2) { + return "\\" + x2; } return "\\x" + (n < 16 ? "0" : "") + $toUpperCase.call(n.toString(16)); } @@ -9687,9 +9687,9 @@ function requireObjectInspect() { } } if (typeof gOPS === "function") { - for (var j = 0; j < syms.length; j++) { - if (isEnumerable.call(obj, syms[j])) { - xs.push("[" + inspect(syms[j]) + "]: " + inspect(obj[syms[j]], obj)); + for (var j2 = 0; j2 < syms.length; j2++) { + if (isEnumerable.call(obj, syms[j2])) { + xs.push("[" + inspect(syms[j2]) + "]: " + inspect(obj[syms[j2]], obj)); } } } @@ -10044,9 +10044,9 @@ function requireUtils() { var obj = item.obj[item.prop]; if (isArray2(obj)) { var compacted = []; - for (var j = 0; j < obj.length; ++j) { - if (typeof obj[j] !== "undefined") { - compacted[compacted.length] = obj[j]; + for (var j2 = 0; j2 < obj.length; ++j2) { + if (typeof obj[j2] !== "undefined") { + compacted[compacted.length] = obj[j2]; } } item.obj[item.prop] = compacted; @@ -10092,9 +10092,9 @@ function requireUtils() { if (isOverflow(source)) { var sourceKeys = Object.keys(source); var result = options && options.plainObjects ? { __proto__: null, 0: target } : { 0: target }; - for (var m = 0; m < sourceKeys.length; m++) { - var oldKey = parseInt(sourceKeys[m], 10); - result[oldKey + 1] = source[sourceKeys[m]]; + for (var m2 = 0; m2 < sourceKeys.length; m2++) { + var oldKey = parseInt(sourceKeys[m2], 10); + result[oldKey + 1] = source[sourceKeys[m2]]; } return markOverflow(result, getMaxIndex(source) + 1); } @@ -10176,8 +10176,8 @@ function requireUtils() { }); } var out = ""; - for (var j = 0; j < string2.length; j += limit) { - var segment = string2.length >= limit ? string2.slice(j, j + limit) : string2; + for (var j2 = 0; j2 < string2.length; j2 += limit) { + var segment = string2.length >= limit ? string2.slice(j2, j2 + limit) : string2; var arr = []; for (var i = 0; i < segment.length; ++i) { var c = segment.charCodeAt(i); @@ -10212,8 +10212,8 @@ function requireUtils() { var item = queue[i]; var obj = item.obj[item.prop]; var keys2 = Object.keys(obj); - for (var j = 0; j < keys2.length; ++j) { - var key = keys2[j]; + for (var j2 = 0; j2 < keys2.length; ++j2) { + var key = keys2[j2]; var val = obj[key]; if (typeof val === "object" && val !== null && refs.indexOf(val) === -1) { queue[queue.length] = { obj, prop: key }; @@ -10324,8 +10324,8 @@ function requireStringify() { skipNulls: false, strictNullHandling: false }; - var isNonNullishPrimitive = function isNonNullishPrimitive2(v) { - return typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "symbol" || typeof v === "bigint"; + var isNonNullishPrimitive = function isNonNullishPrimitive2(v2) { + return typeof v2 === "string" || typeof v2 === "number" || typeof v2 === "boolean" || typeof v2 === "symbol" || typeof v2 === "bigint"; }; var sentinel = {}; var stringify2 = function stringify3(object, prefix, generateArrayPrefix, commaRoundTrip, allowEmptyArrays, strictNullHandling, skipNulls, encodeDotInKeys, encoder, filter2, sort, allowDots, serializeDate, format, formatter, encodeValuesOnly, charset, sideChannel2) { @@ -10393,8 +10393,8 @@ function requireStringify() { if (allowEmptyArrays && isArray2(obj) && obj.length === 0) { return adjustedPrefix + "[]"; } - for (var j = 0; j < objKeys.length; ++j) { - var key = objKeys[j]; + for (var j2 = 0; j2 < objKeys.length; ++j2) { + var key = objKeys[j2]; var value = typeof key === "object" && key && typeof key.value !== "undefined" ? key.value : obj[key]; if (skipNulls && value === null) { continue; @@ -11134,7 +11134,7 @@ function requireNprogress() { } var nprogressExports = requireNprogress(); const u = /* @__PURE__ */ getDefaultExportFromCjs(nprogressExports); -function T(t, e) { +function T$1(t, e) { let i; return function(...r) { clearTimeout(i), i = setTimeout(() => t.apply(this, r), e); @@ -11143,20 +11143,20 @@ function T(t, e) { function f(t, e) { return document.dispatchEvent(new CustomEvent(`inertia:${t}`, e)); } -var H$1 = (t) => f("before", { cancelable: true, detail: { visit: t } }), q = (t) => f("error", { detail: { errors: t } }), $ = (t) => f("exception", { cancelable: true, detail: { exception: t } }), N$1 = (t) => f("finish", { detail: { visit: t } }), W$1 = (t) => f("invalid", { cancelable: true, detail: { response: t } }), P = (t) => f("navigate", { detail: { page: t } }), K$1 = (t) => f("progress", { detail: { progress: t } }), X$1 = (t) => f("start", { detail: { visit: t } }), B = (t) => f("success", { detail: { page: t } }); -function I(t) { - return t instanceof File || t instanceof Blob || t instanceof FileList && t.length > 0 || t instanceof FormData && Array.from(t.values()).some((e) => I(e)) || typeof t == "object" && t !== null && Object.values(t).some((e) => I(e)); +var H$2 = (t) => f("before", { cancelable: true, detail: { visit: t } }), q$1 = (t) => f("error", { detail: { errors: t } }), $$1 = (t) => f("exception", { cancelable: true, detail: { exception: t } }), N$2 = (t) => f("finish", { detail: { visit: t } }), W$2 = (t) => f("invalid", { cancelable: true, detail: { response: t } }), P$1 = (t) => f("navigate", { detail: { page: t } }), K$2 = (t) => f("progress", { detail: { progress: t } }), X$2 = (t) => f("start", { detail: { visit: t } }), B$1 = (t) => f("success", { detail: { page: t } }); +function I$1(t) { + return t instanceof File || t instanceof Blob || t instanceof FileList && t.length > 0 || t instanceof FormData && Array.from(t.values()).some((e) => I$1(e)) || typeof t == "object" && t !== null && Object.values(t).some((e) => I$1(e)); } function k(t, e = new FormData(), i = null) { t = t || {}; - for (let r in t) Object.prototype.hasOwnProperty.call(t, r) && z(e, J$1(i, r), t[r]); + for (let r in t) Object.prototype.hasOwnProperty.call(t, r) && z$1(e, J$2(i, r), t[r]); return e; } -function J$1(t, e) { +function J$2(t, e) { return t ? t + "[" + e + "]" : e; } -function z(t, e, i) { - if (Array.isArray(i)) return Array.from(i.keys()).forEach((r) => z(t, J$1(e, r.toString()), i[r])); +function z$1(t, e, i) { + if (Array.isArray(i)) return Array.from(i.keys()).forEach((r) => z$1(t, J$2(e, r.toString()), i[r])); if (i instanceof Date) return t.append(e, i.toISOString()); if (i instanceof File) return t.append(e, i, i.name); if (i instanceof Blob) return t.append(e, i); @@ -11166,7 +11166,7 @@ function z(t, e, i) { if (i == null) return t.append(e, ""); k(i, t, e); } -var _ = { modal: null, listener: null, show(t) { +var _$1 = { modal: null, listener: null, show(t) { typeof t == "object" && (t = `All Inertia requests must receive a valid Inertia response, however a plain JSON response was received.
${JSON.stringify(t)}`); let e = document.createElement("html"); e.innerHTML = t, e.querySelectorAll("a").forEach((r) => r.setAttribute("target", "_top")), this.modal = document.createElement("div"), this.modal.style.position = "fixed", this.modal.style.width = "100vw", this.modal.style.height = "100vh", this.modal.style.padding = "50px", this.modal.style.boxSizing = "border-box", this.modal.style.backgroundColor = "rgba(0, 0, 0, .6)", this.modal.style.zIndex = 2e5, this.modal.addEventListener("click", () => this.hide()); @@ -11178,21 +11178,21 @@ var _ = { modal: null, listener: null, show(t) { }, hideOnEscape(t) { t.keyCode === 27 && this.hide(); } }; -function b(t) { +function b$1(t) { return new URL(t.toString(), window.location.toString()); } -function D(t, e, i, r = "brackets") { - let s2 = /^https?:\/\//.test(e.toString()), l = s2 || e.toString().startsWith("/"), h = !l && !e.toString().startsWith("#") && !e.toString().startsWith("?"), g2 = e.toString().includes("?") || t === "get" && Object.keys(i).length, m = e.toString().includes("#"), c = new URL(e.toString(), "http://localhost"); - return t === "get" && Object.keys(i).length && (c.search = libExports.stringify(oe(libExports.parse(c.search, { ignoreQueryPrefix: true }), i), { encodeValuesOnly: true, arrayFormat: r }), i = {}), [[s2 ? `${c.protocol}//${c.host}` : "", l ? c.pathname : "", h ? c.pathname.substring(1) : "", g2 ? c.search : "", m ? c.hash : ""].join(""), i]; +function D$1(t, e, i, r = "brackets") { + let s2 = /^https?:\/\//.test(e.toString()), l3 = s2 || e.toString().startsWith("/"), h = !l3 && !e.toString().startsWith("#") && !e.toString().startsWith("?"), g2 = e.toString().includes("?") || t === "get" && Object.keys(i).length, m2 = e.toString().includes("#"), c = new URL(e.toString(), "http://localhost"); + return t === "get" && Object.keys(i).length && (c.search = libExports.stringify(oe$1(libExports.parse(c.search, { ignoreQueryPrefix: true }), i), { encodeValuesOnly: true, arrayFormat: r }), i = {}), [[s2 ? `${c.protocol}//${c.host}` : "", l3 ? c.pathname : "", h ? c.pathname.substring(1) : "", g2 ? c.search : "", m2 ? c.hash : ""].join(""), i]; } -function E(t) { +function E$1(t) { return t = new URL(t.href), t.hash = "", t; } -var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAgent), Z = (t) => { +var C$1 = typeof window > "u", Y$1 = !C$1 && /CriOS/.test(window.navigator.userAgent), Z = (t) => { requestAnimationFrame(() => { requestAnimationFrame(t); }); -}, F = class { +}, F$1 = class F { constructor() { this.visitId = null; } @@ -11207,10 +11207,10 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge } handleInitialPageVisit(e) { let i = window.location.hash; - this.page.url.includes(i) || (this.page.url += i), this.setPage(e, { preserveScroll: true, preserveState: true }).then(() => P(e)); + this.page.url.includes(i) || (this.page.url += i), this.setPage(e, { preserveScroll: true, preserveState: true }).then(() => P$1(e)); } setupEventListeners() { - window.addEventListener("popstate", this.handlePopstateEvent.bind(this)), document.addEventListener("scroll", T(this.handleScrollEvent.bind(this), 100), true); + window.addEventListener("popstate", this.handlePopstateEvent.bind(this)), document.addEventListener("scroll", T$1(this.handleScrollEvent.bind(this), 100), true); } scrollRegions() { return document.querySelectorAll("[scroll-region]"); @@ -11242,13 +11242,13 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge } handleBackForwardVisit(e) { window.history.state.version = e.version, this.setPage(window.history.state, { preserveScroll: true, preserveState: true }).then(() => { - this.restoreScrollPositions(), P(e); + this.restoreScrollPositions(), P$1(e); }); } locationVisit(e, i) { try { let r = { preserveScroll: i }; - window.sessionStorage.setItem("inertiaLocationVisit", JSON.stringify(r)), window.location.href = e.href, E(window.location).href === E(e).href && window.location.reload(); + window.sessionStorage.setItem("inertiaLocationVisit", JSON.stringify(r)), window.location.href = e.href, E$1(window.location).href === E$1(e).href && window.location.reload(); } catch { return false; } @@ -11263,7 +11263,7 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge handleLocationVisit(e) { let i = JSON.parse(window.sessionStorage.getItem("inertiaLocationVisit") || ""); window.sessionStorage.removeItem("inertiaLocationVisit"), e.url += window.location.hash, e.rememberedState = window.history.state?.rememberedState ?? {}, e.scrollRegions = window.history.state?.scrollRegions ?? [], this.setPage(e, { preserveScroll: i.preserveScroll, preserveState: true }).then(() => { - i.preserveScroll && this.restoreScrollPositions(), P(e); + i.preserveScroll && this.restoreScrollPositions(), P$1(e); }); } isLocationVisitResponse(e) { @@ -11276,10 +11276,10 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge return this.visitId = {}, this.visitId; } cancelVisit(e, { cancelled: i = false, interrupted: r = false }) { - e && !e.completed && !e.cancelled && !e.interrupted && (e.cancelToken.abort(), e.onCancel(), e.completed = false, e.cancelled = i, e.interrupted = r, N$1(e), e.onFinish(e)); + e && !e.completed && !e.cancelled && !e.interrupted && (e.cancelToken.abort(), e.onCancel(), e.completed = false, e.cancelled = i, e.interrupted = r, N$2(e), e.onFinish(e)); } finishVisit(e) { - !e.cancelled && !e.interrupted && (e.completed = true, e.cancelled = false, e.interrupted = false, N$1(e), e.onFinish(e)); + !e.cancelled && !e.interrupted && (e.completed = true, e.cancelled = false, e.interrupted = false, N$2(e), e.onFinish(e)); } resolvePreserveOption(e, i) { return typeof e == "function" ? e(i) : e === "errors" ? Object.keys(i.props.errors || {}).length > 0 : e; @@ -11287,82 +11287,82 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge cancel() { this.activeVisit && this.cancelVisit(this.activeVisit, { cancelled: true }); } - visit(e, { method: i = "get", data: r = {}, replace: s2 = false, preserveScroll: l = false, preserveState: h = false, only: g2 = [], except: m = [], headers: c = {}, errorBag: o = "", forceFormData: v = false, onCancelToken: L = () => { + visit(e, { method: i = "get", data: r = {}, replace: s2 = false, preserveScroll: l3 = false, preserveState: h = false, only: g2 = [], except: m2 = [], headers: c = {}, errorBag: o = "", forceFormData: v2 = false, onCancelToken: L2 = () => { }, onBefore: d2 = () => { }, onStart: p = () => { - }, onProgress: x = () => { - }, onFinish: y = () => { + }, onProgress: x2 = () => { + }, onFinish: y2 = () => { }, onCancel: ne = () => { - }, onSuccess: U = () => { + }, onSuccess: U2 = () => { }, onError: G2 = () => { - }, queryStringArrayFormat: A = "brackets" } = {}) { - let S = typeof e == "string" ? b(e) : e; - if ((I(r) || v) && !(r instanceof FormData) && (r = k(r)), !(r instanceof FormData)) { - let [n, a] = D(i, S, r, A); - S = b(n), r = a; + }, queryStringArrayFormat: A2 = "brackets" } = {}) { + let S = typeof e == "string" ? b$1(e) : e; + if ((I$1(r) || v2) && !(r instanceof FormData) && (r = k(r)), !(r instanceof FormData)) { + let [n, a] = D$1(i, S, r, A2); + S = b$1(n), r = a; } - let R = { url: S, method: i, data: r, replace: s2, preserveScroll: l, preserveState: h, only: g2, except: m, headers: c, errorBag: o, forceFormData: v, queryStringArrayFormat: A, cancelled: false, completed: false, interrupted: false }; - if (d2(R) === false || !H$1(R)) return; + let R = { url: S, method: i, data: r, replace: s2, preserveScroll: l3, preserveState: h, only: g2, except: m2, headers: c, errorBag: o, forceFormData: v2, queryStringArrayFormat: A2, cancelled: false, completed: false, interrupted: false }; + if (d2(R) === false || !H$2(R)) return; this.activeVisit && this.cancelVisit(this.activeVisit, { interrupted: true }), this.saveScrollPositions(); let M2 = this.createVisitId(); - this.activeVisit = { ...R, onCancelToken: L, onBefore: d2, onStart: p, onProgress: x, onFinish: y, onCancel: ne, onSuccess: U, onError: G2, queryStringArrayFormat: A, cancelToken: new AbortController() }, L({ cancel: () => { + this.activeVisit = { ...R, onCancelToken: L2, onBefore: d2, onStart: p, onProgress: x2, onFinish: y2, onCancel: ne, onSuccess: U2, onError: G2, queryStringArrayFormat: A2, cancelToken: new AbortController() }, L2({ cancel: () => { this.activeVisit && this.cancelVisit(this.activeVisit, { cancelled: true }); - } }), X$1(R), p(R); - let j = !!(g2.length || m.length); - axios({ method: i, url: E(S).href, data: i === "get" ? {} : r, params: i === "get" ? r : {}, signal: this.activeVisit.cancelToken.signal, headers: { ...c, Accept: "text/html, application/xhtml+xml", "X-Requested-With": "XMLHttpRequest", "X-Inertia": true, ...j ? { "X-Inertia-Partial-Component": this.page.component } : {}, ...g2.length ? { "X-Inertia-Partial-Data": g2.join(",") } : {}, ...m.length ? { "X-Inertia-Partial-Except": m.join(",") } : {}, ...o && o.length ? { "X-Inertia-Error-Bag": o } : {}, ...this.page.version ? { "X-Inertia-Version": this.page.version } : {} }, onUploadProgress: (n) => { - r instanceof FormData && (n.percentage = n.progress ? Math.round(n.progress * 100) : 0, K$1(n), x(n)); + } }), X$2(R), p(R); + let j2 = !!(g2.length || m2.length); + axios({ method: i, url: E$1(S).href, data: i === "get" ? {} : r, params: i === "get" ? r : {}, signal: this.activeVisit.cancelToken.signal, headers: { ...c, Accept: "text/html, application/xhtml+xml", "X-Requested-With": "XMLHttpRequest", "X-Inertia": true, ...j2 ? { "X-Inertia-Partial-Component": this.page.component } : {}, ...g2.length ? { "X-Inertia-Partial-Data": g2.join(",") } : {}, ...m2.length ? { "X-Inertia-Partial-Except": m2.join(",") } : {}, ...o && o.length ? { "X-Inertia-Error-Bag": o } : {}, ...this.page.version ? { "X-Inertia-Version": this.page.version } : {} }, onUploadProgress: (n) => { + r instanceof FormData && (n.percentage = n.progress ? Math.round(n.progress * 100) : 0, K$2(n), x2(n)); } }).then((n) => { if (!this.isInertiaResponse(n)) return Promise.reject({ response: n }); let a = n.data; - j && a.component === this.page.component && (a.props = { ...this.page.props, ...a.props }), l = this.resolvePreserveOption(l, a), h = this.resolvePreserveOption(h, a), h && window.history.state?.rememberedState && a.component === this.page.component && (a.rememberedState = window.history.state.rememberedState); - let w2 = S, V2 = b(a.url); - return w2.hash && !V2.hash && E(w2).href === V2.href && (V2.hash = w2.hash, a.url = V2.href), this.setPage(a, { visitId: M2, replace: s2, preserveScroll: l, preserveState: h }); + j2 && a.component === this.page.component && (a.props = { ...this.page.props, ...a.props }), l3 = this.resolvePreserveOption(l3, a), h = this.resolvePreserveOption(h, a), h && window.history.state?.rememberedState && a.component === this.page.component && (a.rememberedState = window.history.state.rememberedState); + let w2 = S, V2 = b$1(a.url); + return w2.hash && !V2.hash && E$1(w2).href === V2.href && (V2.hash = w2.hash, a.url = V2.href), this.setPage(a, { visitId: M2, replace: s2, preserveScroll: l3, preserveState: h }); }).then(() => { let n = this.page.props.errors || {}; if (Object.keys(n).length > 0) { let a = o ? n[o] ? n[o] : {} : n; - return q(a), G2(a); + return q$1(a), G2(a); } - return B(this.page), U(this.page); + return B$1(this.page), U2(this.page); }).catch((n) => { if (this.isInertiaResponse(n.response)) return this.setPage(n.response.data, { visitId: M2 }); if (this.isLocationVisitResponse(n.response)) { - let a = b(n.response.headers["x-inertia-location"]), w2 = S; - w2.hash && !a.hash && E(w2).href === a.href && (a.hash = w2.hash), this.locationVisit(a, l === true); - } else if (n.response) W$1(n.response) && _.show(n.response.data); + let a = b$1(n.response.headers["x-inertia-location"]), w2 = S; + w2.hash && !a.hash && E$1(w2).href === a.href && (a.hash = w2.hash), this.locationVisit(a, l3 === true); + } else if (n.response) W$2(n.response) && _$1.show(n.response.data); else return Promise.reject(n); }).then(() => { this.activeVisit && this.finishVisit(this.activeVisit); }).catch((n) => { if (!axios.isCancel(n)) { - let a = $(n); + let a = $$1(n); if (this.activeVisit && this.finishVisit(this.activeVisit), a) return Promise.reject(n); } }); } - setPage(e, { visitId: i = this.createVisitId(), replace: r = false, preserveScroll: s2 = false, preserveState: l = false } = {}) { + setPage(e, { visitId: i = this.createVisitId(), replace: r = false, preserveScroll: s2 = false, preserveState: l3 = false } = {}) { return Promise.resolve(this.resolveComponent(e.component)).then((h) => { - i === this.visitId && (e.scrollRegions = this.page.scrollRegions || [], e.rememberedState = e.rememberedState || {}, r = r || b(e.url).href === window.location.href, r ? this.replaceState(e) : this.pushState(e), this.swapComponent({ component: h, page: e, preserveState: l }).then(() => { - s2 ? this.restoreScrollPositions() : this.resetScrollPositions(), r || P(e); + i === this.visitId && (e.scrollRegions = this.page.scrollRegions || [], e.rememberedState = e.rememberedState || {}, r = r || b$1(e.url).href === window.location.href, r ? this.replaceState(e) : this.pushState(e), this.swapComponent({ component: h, page: e, preserveState: l3 }).then(() => { + s2 ? this.restoreScrollPositions() : this.resetScrollPositions(), r || P$1(e); })); }); } pushState(e) { - this.page = e, Y ? setTimeout(() => window.history.pushState(e, "", e.url)) : window.history.pushState(e, "", e.url); + this.page = e, Y$1 ? setTimeout(() => window.history.pushState(e, "", e.url)) : window.history.pushState(e, "", e.url); } replaceState(e) { - this.page = e, Y ? setTimeout(() => window.history.replaceState(e, "", e.url)) : window.history.replaceState(e, "", e.url); + this.page = e, Y$1 ? setTimeout(() => window.history.replaceState(e, "", e.url)) : window.history.replaceState(e, "", e.url); } handlePopstateEvent(e) { if (e.state !== null) { let i = e.state, r = this.createVisitId(); Promise.resolve(this.resolveComponent(i.component)).then((s2) => { r === this.visitId && (this.page = i, this.swapComponent({ component: s2, page: i, preserveState: false }).then(() => { - this.restoreScrollPositions(), P(i); + this.restoreScrollPositions(), P$1(i); })); }); } else { - let i = b(this.page.url); + let i = b$1(this.page.url); i.hash = window.location.hash, this.replaceState({ ...this.page, url: i.href }), this.resetScrollPositions(); } } @@ -11397,13 +11397,13 @@ var C$1 = typeof window > "u", Y = !C$1 && /CriOS/.test(window.navigator.userAge if (C$1) return () => { }; let r = (s2) => { - let l = i(s2); - s2.cancelable && !s2.defaultPrevented && l === false && s2.preventDefault(); + let l3 = i(s2); + s2.cancelable && !s2.defaultPrevented && l3 === false && s2.preventDefault(); }; return document.addEventListener(`inertia:${e}`, r), () => document.removeEventListener(`inertia:${e}`, r); } }; -var se = { buildDOMElement(t) { +var se$1 = { buildDOMElement(t) { let e = document.createElement("template"); e.innerHTML = t; let i = e.content.firstChild; @@ -11417,7 +11417,7 @@ var se = { buildDOMElement(t) { }, findMatchingElementIndex(t, e) { let i = t.getAttribute("inertia"); return i !== null ? e.findIndex((r) => r.getAttribute("inertia") === i) : -1; -}, update: T(function(t) { +}, update: T$1(function(t) { let e = t.map((r) => this.buildDOMElement(r)); Array.from(document.head.childNodes).filter((r) => this.isInertiaManagedElement(r)).forEach((r) => { let s2 = this.findMatchingElementIndex(r, e); @@ -11425,57 +11425,57 @@ var se = { buildDOMElement(t) { r?.parentNode?.removeChild(r); return; } - let l = e.splice(s2, 1)[0]; - l && !r.isEqualNode(l) && r?.parentNode?.replaceChild(l, r); + let l3 = e.splice(s2, 1)[0]; + l3 && !r.isEqualNode(l3) && r?.parentNode?.replaceChild(l3, r); }), e.forEach((r) => document.head.appendChild(r)); }, 1) }; function ee(t, e, i) { let r = {}, s2 = 0; - function l() { + function l3() { let o = s2 += 1; return r[o] = [], o.toString(); } function h(o) { o === null || Object.keys(r).indexOf(o) === -1 || (delete r[o], c()); } - function g2(o, v = []) { - o !== null && Object.keys(r).indexOf(o) > -1 && (r[o] = v), c(); + function g2(o, v2 = []) { + o !== null && Object.keys(r).indexOf(o) > -1 && (r[o] = v2), c(); } - function m() { - let o = e(""), v = { ...o ? { title: `${o}` } : {} }, L = Object.values(r).reduce((d2, p) => d2.concat(p), []).reduce((d2, p) => { + function m2() { + let o = e(""), v2 = { ...o ? { title: `${o}` } : {} }, L2 = Object.values(r).reduce((d2, p) => d2.concat(p), []).reduce((d2, p) => { if (p.indexOf("<") === -1) return d2; if (p.indexOf("]+>)(.*?)(<\/title>)/); - return d2.title = y ? `${y[1]}${e(y[2])}${y[3]}` : p, d2; + let y2 = p.match(/(<title [^>]+>)(.*?)(<\/title>)/); + return d2.title = y2 ? `${y2[1]}${e(y2[2])}${y2[3]}` : p, d2; } - let x = p.match(/ inertia="[^"]+"/); - return x ? d2[x[0]] = p : d2[Object.keys(d2).length] = p, d2; - }, v); - return Object.values(L); + let x2 = p.match(/ inertia="[^"]+"/); + return x2 ? d2[x2[0]] = p : d2[Object.keys(d2).length] = p, d2; + }, v2); + return Object.values(L2); } function c() { - t ? i(m()) : se.update(m()); + t ? i(m2()) : se$1.update(m2()); } return c(), { forceUpdate: c, createProvider: function() { - let o = l(); - return { update: (v) => g2(o, v), disconnect: () => h(o) }; + let o = l3(); + return { update: (v2) => g2(o, v2), disconnect: () => h(o) }; } }; } var te = null; -function ae(t) { - document.addEventListener("inertia:start", le.bind(null, t)), document.addEventListener("inertia:progress", ce), document.addEventListener("inertia:finish", de); +function ae$1(t) { + document.addEventListener("inertia:start", le$1.bind(null, t)), document.addEventListener("inertia:progress", ce$1), document.addEventListener("inertia:finish", de$1); } -function le(t) { +function le$1(t) { te = setTimeout(() => u.start(), t); } -function ce(t) { +function ce$1(t) { u.isStarted() && t.detail.progress?.percentage && u.set(Math.max(u.status, t.detail.progress.percentage / 100 * 0.9)); } -function de(t) { +function de$1(t) { if (clearTimeout(te), u.isStarted()) t.detail.visit.completed ? u.done() : t.detail.visit.interrupted ? u.set(0) : t.detail.visit.cancelled && (u.done(), u.remove()); else return; } -function pe(t) { +function pe$1(t) { let e = document.createElement("style"); e.type = "text/css", e.textContent = ` #nprogress { @@ -11550,14 +11550,14 @@ function pe(t) { } `, document.head.appendChild(e); } -function ie({ delay: t = 250, color: e = "#29d", includeCSS: i = true, showSpinner: r = false } = {}) { - ae(t), u.configure({ showSpinner: r }), i && pe(e); +function ie$1({ delay: t = 250, color: e = "#29d", includeCSS: i = true, showSpinner: r = false } = {}) { + ae$1(t), u.configure({ showSpinner: r }), i && pe$1(e); } -function re$1(t) { +function re$2(t) { let e = t.currentTarget.tagName.toLowerCase() === "a"; return !(t.target && (t?.target).isContentEditable || t.defaultPrevented || e && t.altKey || e && t.ctrlKey || e && t.metaKey || e && t.shiftKey || e && "button" in t && t.button !== 0); } -var Ne = new F(); +var Ne$1 = new F$1(); var lodash_isequal = { exports: {} }; lodash_isequal.exports; var hasRequiredLodash_isequal; @@ -12215,44 +12215,44 @@ function requireLodash_isequal() { return lodash_isequal.exports; } var lodash_isequalExports = requireLodash_isequal(); -const Ae = /* @__PURE__ */ getDefaultExportFromCjs(lodash_isequalExports); -var V = reactExports.createContext(void 0); -V.displayName = "InertiaHeadContext"; -var M = V; -var K = reactExports.createContext(void 0); -K.displayName = "InertiaPageContext"; -var H = K; -function N({ children: l, initialPage: p, initialComponent: a, resolveComponent: i, titleCallback: n, onHeadUpdate: F2 }) { - let [d2, y] = reactExports.useState({ component: a || null, page: p, key: null }), u2 = reactExports.useMemo(() => ee(typeof window > "u", n || ((c) => c), F2 || (() => { +const Ae$1 = /* @__PURE__ */ getDefaultExportFromCjs(lodash_isequalExports); +var V$1 = reactExports.createContext(void 0); +V$1.displayName = "InertiaHeadContext"; +var M$1 = V$1; +var K$1 = reactExports.createContext(void 0); +K$1.displayName = "InertiaPageContext"; +var H$1 = K$1; +function N$1({ children: l3, initialPage: p, initialComponent: a, resolveComponent: i, titleCallback: n, onHeadUpdate: F3 }) { + let [d2, y2] = reactExports.useState({ component: a || null, page: p, key: null }), u2 = reactExports.useMemo(() => ee(typeof window > "u", n || ((c) => c), F3 || (() => { })), []); if (reactExports.useEffect(() => { - Ne.init({ initialPage: p, resolveComponent: i, swapComponent: async ({ component: c, page: e, preserveState: s2 }) => { - y((r) => ({ component: c, page: e, key: s2 ? r.key : Date.now() })); - } }), Ne.on("navigate", () => u2.forceUpdate()); - }, []), !d2.component) return reactExports.createElement(M.Provider, { value: u2 }, reactExports.createElement(H.Provider, { value: d2.page }, null)); - let f2 = l || (({ Component: c, props: e, key: s2 }) => { + Ne$1.init({ initialPage: p, resolveComponent: i, swapComponent: async ({ component: c, page: e, preserveState: s2 }) => { + y2((r) => ({ component: c, page: e, key: s2 ? r.key : Date.now() })); + } }), Ne$1.on("navigate", () => u2.forceUpdate()); + }, []), !d2.component) return reactExports.createElement(M$1.Provider, { value: u2 }, reactExports.createElement(H$1.Provider, { value: d2.page }, null)); + let f2 = l3 || (({ Component: c, props: e, key: s2 }) => { let r = reactExports.createElement(c, { key: s2, ...e }); return typeof c.layout == "function" ? c.layout(r) : Array.isArray(c.layout) ? c.layout.concat(r).reverse().reduce((g2, T2) => reactExports.createElement(T2, { children: g2, ...e })) : r; }); - return reactExports.createElement(M.Provider, { value: u2 }, reactExports.createElement(H.Provider, { value: d2.page }, f2({ Component: d2.component, key: d2.key, props: d2.page.props }))); + return reactExports.createElement(M$1.Provider, { value: u2 }, reactExports.createElement(H$1.Provider, { value: d2.page }, f2({ Component: d2.component, key: d2.key, props: d2.page.props }))); } -N.displayName = "Inertia"; -async function W({ id: l = "app", resolve: p, setup: a, title: i, progress: n = {}, page: F2, render: d2 }) { - let y = typeof window > "u", u2 = y ? null : document.getElementById(l), f2 = F2 || JSON.parse(u2.dataset.page), c = (r) => Promise.resolve(p(r)).then((g2) => g2.default || g2), e = [], s2 = await c(f2.component).then((r) => a({ el: u2, App: N, props: { initialPage: f2, initialComponent: r, resolveComponent: c, titleCallback: i, onHeadUpdate: y ? (g2) => e = g2 : null } })); - if (!y && n && ie(n), y) { - let r = await d2(reactExports.createElement("div", { id: l, "data-page": JSON.stringify(f2) }, s2)); +N$1.displayName = "Inertia"; +async function W$1({ id: l3 = "app", resolve: p, setup: a, title: i, progress: n = {}, page: F3, render: d2 }) { + let y2 = typeof window > "u", u2 = y2 ? null : document.getElementById(l3), f2 = F3 || JSON.parse(u2.dataset.page), c = (r) => Promise.resolve(p(r)).then((g2) => g2.default || g2), e = [], s2 = await c(f2.component).then((r) => a({ el: u2, App: N$1, props: { initialPage: f2, initialComponent: r, resolveComponent: c, titleCallback: i, onHeadUpdate: y2 ? (g2) => e = g2 : null } })); + if (!y2 && n && ie$1(n), y2) { + let r = await d2(reactExports.createElement("div", { id: l3, "data-page": JSON.stringify(f2) }, s2)); return { head: e, body: r }; } } -var Te = function({ children: l, title: p }) { - let a = reactExports.useContext(M), i = reactExports.useMemo(() => a.createProvider(), [a]); +var Te$1 = function({ children: l3, title: p }) { + let a = reactExports.useContext(M$1), i = reactExports.useMemo(() => a.createProvider(), [a]); reactExports.useEffect(() => () => { i.disconnect(); }, [i]); function n(e) { return ["area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"].indexOf(e.type) > -1; } - function F2(e) { + function F3(e) { let s2 = Object.keys(e.props).reduce((r, g2) => { if (["head-key", "children", "dangerouslySetInnerHTML"].includes(g2)) return r; let T2 = e.props[g2]; @@ -12261,79 +12261,79 @@ var Te = function({ children: l, title: p }) { return `<${e.type}${s2}>`; } function d2(e) { - return typeof e.props.children == "string" ? e.props.children : e.props.children.reduce((s2, r) => s2 + y(r), ""); + return typeof e.props.children == "string" ? e.props.children : e.props.children.reduce((s2, r) => s2 + y2(r), ""); } - function y(e) { - let s2 = F2(e); + function y2(e) { + let s2 = F3(e); return e.props.children && (s2 += d2(e)), e.props.dangerouslySetInnerHTML && (s2 += e.props.dangerouslySetInnerHTML.__html), n(e) || (s2 += `</${e.type}>`), s2; } function u2(e) { return React.cloneElement(e, { inertia: e.props["head-key"] !== void 0 ? e.props["head-key"] : "" }); } function f2(e) { - return y(u2(e)); + return y2(u2(e)); } function c(e) { let s2 = React.Children.toArray(e).filter((r) => r).map((r) => f2(r)); return p && !s2.find((r) => r.startsWith("<title")) && s2.push(`<title inertia>${p}`), s2; } - return i.update(c(l)), null; -}, Se = Te; + return i.update(c(l3)), null; +}, Se$1 = Te$1; var C = () => { -}, J = reactExports.forwardRef(({ children: l, as: p = "a", data: a = {}, href: i, method: n = "get", preserveScroll: F2 = false, preserveState: d2 = null, replace: y = false, only: u2 = [], except: f2 = [], headers: c = {}, queryStringArrayFormat: e = "brackets", onClick: s2 = C, onCancelToken: r = C, onBefore: g2 = C, onStart: T2 = C, onProgress: O = C, onFinish: k2 = C, onCancel: L = C, onSuccess: R = C, onError: B2 = C, ...I2 }, A) => { - let v = reactExports.useCallback((E2) => { - s2(E2), re$1(E2) && (E2.preventDefault(), Ne.visit(i, { data: a, method: n, preserveScroll: F2, preserveState: d2 ?? n !== "get", replace: y, only: u2, except: f2, headers: c, onCancelToken: r, onBefore: g2, onStart: T2, onProgress: O, onFinish: k2, onCancel: L, onSuccess: R, onError: B2 })); - }, [a, i, n, F2, d2, y, u2, f2, c, s2, r, g2, T2, O, k2, L, R, B2]); +}, J$1 = reactExports.forwardRef(({ children: l3, as: p = "a", data: a = {}, href: i, method: n = "get", preserveScroll: F3 = false, preserveState: d2 = null, replace: y2 = false, only: u2 = [], except: f2 = [], headers: c = {}, queryStringArrayFormat: e = "brackets", onClick: s2 = C, onCancelToken: r = C, onBefore: g2 = C, onStart: T2 = C, onProgress: O2 = C, onFinish: k2 = C, onCancel: L2 = C, onSuccess: R = C, onError: B2 = C, ...I2 }, A2) => { + let v2 = reactExports.useCallback((E2) => { + s2(E2), re$2(E2) && (E2.preventDefault(), Ne$1.visit(i, { data: a, method: n, preserveScroll: F3, preserveState: d2 ?? n !== "get", replace: y2, only: u2, except: f2, headers: c, onCancelToken: r, onBefore: g2, onStart: T2, onProgress: O2, onFinish: k2, onCancel: L2, onSuccess: R, onError: B2 })); + }, [a, i, n, F3, d2, y2, u2, f2, c, s2, r, g2, T2, O2, k2, L2, R, B2]); p = p.toLowerCase(), n = n.toLowerCase(); - let [j, U] = D(n, i || "", a, e); - return i = j, a = U, p === "a" && n !== "get" && console.warn(`Creating POST/PUT/PATCH/DELETE links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues. + let [j2, U2] = D$1(n, i || "", a, e); + return i = j2, a = U2, p === "a" && n !== "get" && console.warn(`Creating POST/PUT/PATCH/DELETE links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues. Please specify a more appropriate element using the "as" attribute. For example: -...`), reactExports.createElement(p, { ...I2, ...p === "a" ? { href: i } : {}, ref: A, onClick: v }, l); +...`), reactExports.createElement(p, { ...I2, ...p === "a" ? { href: i } : {}, ref: A2, onClick: v2 }, l3); }); -J.displayName = "InertiaLink"; -var xe = J; -function w(l, p) { +J$1.displayName = "InertiaLink"; +var xe = J$1; +function w$1(l3, p) { let [a, i] = reactExports.useState(() => { - let n = Ne.restore(p); - return n !== void 0 ? n : l; + let n = Ne$1.restore(p); + return n !== void 0 ? n : l3; }); return reactExports.useEffect(() => { - Ne.remember(a, p); + Ne$1.remember(a, p); }, [a, p]), [a, i]; } -function G(l, p) { - let a = reactExports.useRef(null), i = typeof l == "string" ? l : null, [n, F2] = reactExports.useState((typeof l == "string" ? p : l) || {}), d2 = reactExports.useRef(null), y = reactExports.useRef(null), [u2, f2] = i ? w(n, `${i}:data`) : reactExports.useState(n), [c, e] = i ? w({}, `${i}:errors`) : reactExports.useState({}), [s2, r] = reactExports.useState(false), [g2, T2] = reactExports.useState(false), [O, k2] = reactExports.useState(null), [L, R] = reactExports.useState(false), [B2, I2] = reactExports.useState(false), A = reactExports.useRef((o) => o); +function G$1(l3, p) { + let a = reactExports.useRef(null), i = typeof l3 == "string" ? l3 : null, [n, F3] = reactExports.useState((typeof l3 == "string" ? p : l3) || {}), d2 = reactExports.useRef(null), y2 = reactExports.useRef(null), [u2, f2] = i ? w$1(n, `${i}:data`) : reactExports.useState(n), [c, e] = i ? w$1({}, `${i}:errors`) : reactExports.useState({}), [s2, r] = reactExports.useState(false), [g2, T2] = reactExports.useState(false), [O2, k2] = reactExports.useState(null), [L2, R] = reactExports.useState(false), [B2, I2] = reactExports.useState(false), A2 = reactExports.useRef((o) => o); reactExports.useEffect(() => (a.current = true, () => { a.current = false; }), []); - let v = reactExports.useCallback((o, P2, t = {}) => { - let h = { ...t, onCancelToken: (m) => { - if (d2.current = m, t.onCancelToken) return t.onCancelToken(m); - }, onBefore: (m) => { - if (R(false), I2(false), clearTimeout(y.current), t.onBefore) return t.onBefore(m); - }, onStart: (m) => { - if (T2(true), t.onStart) return t.onStart(m); - }, onProgress: (m) => { - if (k2(m), t.onProgress) return t.onProgress(m); - }, onSuccess: (m) => { - if (a.current && (T2(false), k2(null), e({}), r(false), R(true), I2(true), y.current = setTimeout(() => { + let v2 = reactExports.useCallback((o, P2, t = {}) => { + let h = { ...t, onCancelToken: (m2) => { + if (d2.current = m2, t.onCancelToken) return t.onCancelToken(m2); + }, onBefore: (m2) => { + if (R(false), I2(false), clearTimeout(y2.current), t.onBefore) return t.onBefore(m2); + }, onStart: (m2) => { + if (T2(true), t.onStart) return t.onStart(m2); + }, onProgress: (m2) => { + if (k2(m2), t.onProgress) return t.onProgress(m2); + }, onSuccess: (m2) => { + if (a.current && (T2(false), k2(null), e({}), r(false), R(true), I2(true), y2.current = setTimeout(() => { a.current && I2(false); - }, 2e3)), t.onSuccess) return t.onSuccess(m); - }, onError: (m) => { - if (a.current && (T2(false), k2(null), e(m), r(true)), t.onError) return t.onError(m); + }, 2e3)), t.onSuccess) return t.onSuccess(m2); + }, onError: (m2) => { + if (a.current && (T2(false), k2(null), e(m2), r(true)), t.onError) return t.onError(m2); }, onCancel: () => { if (a.current && (T2(false), k2(null)), t.onCancel) return t.onCancel(); }, onFinish: () => { if (a.current && (T2(false), k2(null)), d2.current = null, t.onFinish) return t.onFinish(); } }; - o === "delete" ? Ne.delete(P2, { ...h, data: A.current(u2) }) : Ne[o](P2, A.current(u2), h); - }, [u2, e]), j = reactExports.useCallback((o, P2) => { + o === "delete" ? Ne$1.delete(P2, { ...h, data: A2.current(u2) }) : Ne$1[o](P2, A2.current(u2), h); + }, [u2, e]), j2 = reactExports.useCallback((o, P2) => { f2(typeof o == "string" ? (t) => ({ ...t, [o]: P2 }) : typeof o == "function" ? (t) => o(t) : o); - }, [f2]), U = reactExports.useCallback((o, P2) => { - F2(typeof o > "u" ? () => u2 : (t) => ({ ...t, ...typeof o == "string" ? { [o]: P2 } : o })); - }, [u2, F2]), E2 = reactExports.useCallback((...o) => { + }, [f2]), U2 = reactExports.useCallback((o, P2) => { + F3(typeof o > "u" ? () => u2 : (t) => ({ ...t, ...typeof o == "string" ? { [o]: P2 } : o })); + }, [u2, F3]), E2 = reactExports.useCallback((...o) => { o.length === 0 ? f2(n) : f2((P2) => Object.keys(n).filter((t) => o.includes(t)).reduce((t, h) => (t[h] = n[h], t), { ...P2 })); }, [f2, n]), Y2 = reactExports.useCallback((o, P2) => { e((t) => { @@ -12342,24 +12342,24 @@ function G(l, p) { }); }, [e, r]), Z2 = reactExports.useCallback((...o) => { e((P2) => { - let t = Object.keys(P2).reduce((h, m) => ({ ...h, ...o.length > 0 && !o.includes(m) ? { [m]: P2[m] } : {} }), {}); + let t = Object.keys(P2).reduce((h, m2) => ({ ...h, ...o.length > 0 && !o.includes(m2) ? { [m2]: P2[m2] } : {} }), {}); return r(Object.keys(t).length > 0), t; }); }, [e, r]), D2 = (o) => (P2, t) => { - v(o, P2, t); - }, ee2 = reactExports.useCallback(D2("get"), [v]), te2 = reactExports.useCallback(D2("post"), [v]), re2 = reactExports.useCallback(D2("put"), [v]), oe2 = reactExports.useCallback(D2("patch"), [v]), ne = reactExports.useCallback(D2("delete"), [v]), ae2 = reactExports.useCallback(() => { + v2(o, P2, t); + }, ee2 = reactExports.useCallback(D2("get"), [v2]), te2 = reactExports.useCallback(D2("post"), [v2]), re2 = reactExports.useCallback(D2("put"), [v2]), oe2 = reactExports.useCallback(D2("patch"), [v2]), ne = reactExports.useCallback(D2("delete"), [v2]), ae2 = reactExports.useCallback(() => { d2.current && d2.current.cancel(); }, []), se2 = reactExports.useCallback((o) => { - A.current = o; + A2.current = o; }, []); - return { data: u2, setData: j, isDirty: !Ae(u2, n), errors: c, hasErrors: s2, processing: g2, progress: O, wasSuccessful: L, recentlySuccessful: B2, transform: se2, setDefaults: U, reset: E2, setError: Y2, clearErrors: Z2, submit: v, get: ee2, post: te2, put: re2, patch: oe2, delete: ne, cancel: ae2 }; + return { data: u2, setData: j2, isDirty: !Ae$1(u2, n), errors: c, hasErrors: s2, processing: g2, progress: O2, wasSuccessful: L2, recentlySuccessful: B2, transform: se2, setDefaults: U2, reset: E2, setError: Y2, clearErrors: Z2, submit: v2, get: ee2, post: te2, put: re2, patch: oe2, delete: ne, cancel: ae2 }; } -function X() { - let l = reactExports.useContext(H); - if (!l) throw new Error("usePage must be used within the Inertia component"); - return l; +function X$1() { + let l3 = reactExports.useContext(H$1); + if (!l3) throw new Error("usePage must be used within the Inertia component"); + return l3; } -var At = Ne; +var At = Ne$1; function normalizeJsonLd(input) { if (!input) return []; return (Array.isArray(input) ? input : [input]).filter((schema) => schema && typeof schema === "object"); @@ -12382,7 +12382,7 @@ function SeoHead({ seo = {}, title = null, description = null, jsonLd = null }) const twitterDescription = seo?.twitter_description || ogDescription; const twitterImage = seo?.twitter_image || ogImage || null; const schemas = [...normalizeJsonLd(seo?.json_ld), ...normalizeJsonLd(jsonLd)]; - return /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, metaTitle), metaDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "description", name: "description", content: metaDescription }) : null, keywords ? /* @__PURE__ */ React.createElement("meta", { "head-key": "keywords", name: "keywords", content: keywords }) : null, robots ? /* @__PURE__ */ React.createElement("meta", { "head-key": "robots", name: "robots", content: robots }) : null, canonical ? /* @__PURE__ */ React.createElement("link", { "head-key": "canonical", rel: "canonical", href: canonical }) : null, prev ? /* @__PURE__ */ React.createElement("link", { "head-key": "prev", rel: "prev", href: prev }) : null, next ? /* @__PURE__ */ React.createElement("link", { "head-key": "next", rel: "next", href: next }) : null, /* @__PURE__ */ React.createElement("meta", { "head-key": "og:site_name", property: "og:site_name", content: seo?.og_site_name || "Skinbase" }), /* @__PURE__ */ React.createElement("meta", { "head-key": "og:type", property: "og:type", content: ogType }), /* @__PURE__ */ React.createElement("meta", { "head-key": "og:title", property: "og:title", content: ogTitle }), ogDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:description", property: "og:description", content: ogDescription }) : null, ogUrl ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:url", property: "og:url", content: ogUrl }) : null, ogImage ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image", property: "og:image", content: ogImage }) : null, seo?.og_image_alt ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:alt", property: "og:image:alt", content: seo.og_image_alt }) : null, seo?.og_image_width ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:width", property: "og:image:width", content: String(seo.og_image_width) }) : null, seo?.og_image_height ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:height", property: "og:image:height", content: String(seo.og_image_height) }) : null, /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:card", name: "twitter:card", content: twitterCard }), /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:title", name: "twitter:title", content: twitterTitle }), twitterDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:description", name: "twitter:description", content: twitterDescription }) : null, twitterImage ? /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:image", name: "twitter:image", content: twitterImage }) : null, schemas.map((schema, index2) => { + return /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, metaTitle), metaDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "description", name: "description", content: metaDescription }) : null, keywords ? /* @__PURE__ */ React.createElement("meta", { "head-key": "keywords", name: "keywords", content: keywords }) : null, robots ? /* @__PURE__ */ React.createElement("meta", { "head-key": "robots", name: "robots", content: robots }) : null, canonical ? /* @__PURE__ */ React.createElement("link", { "head-key": "canonical", rel: "canonical", href: canonical }) : null, prev ? /* @__PURE__ */ React.createElement("link", { "head-key": "prev", rel: "prev", href: prev }) : null, next ? /* @__PURE__ */ React.createElement("link", { "head-key": "next", rel: "next", href: next }) : null, /* @__PURE__ */ React.createElement("meta", { "head-key": "og:site_name", property: "og:site_name", content: seo?.og_site_name || "Skinbase" }), /* @__PURE__ */ React.createElement("meta", { "head-key": "og:type", property: "og:type", content: ogType }), /* @__PURE__ */ React.createElement("meta", { "head-key": "og:title", property: "og:title", content: ogTitle }), ogDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:description", property: "og:description", content: ogDescription }) : null, ogUrl ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:url", property: "og:url", content: ogUrl }) : null, ogImage ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image", property: "og:image", content: ogImage }) : null, seo?.og_image_alt ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:alt", property: "og:image:alt", content: seo.og_image_alt }) : null, seo?.og_image_width ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:width", property: "og:image:width", content: String(seo.og_image_width) }) : null, seo?.og_image_height ? /* @__PURE__ */ React.createElement("meta", { "head-key": "og:image:height", property: "og:image:height", content: String(seo.og_image_height) }) : null, /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:card", name: "twitter:card", content: twitterCard }), /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:title", name: "twitter:title", content: twitterTitle }), twitterDescription ? /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:description", name: "twitter:description", content: twitterDescription }) : null, twitterImage ? /* @__PURE__ */ React.createElement("meta", { "head-key": "twitter:image", name: "twitter:image", content: twitterImage }) : null, schemas.map((schema, index2) => { const schemaType = typeof schema?.["@type"] === "string" ? schema["@type"] : "schema"; return /* @__PURE__ */ React.createElement( "script", @@ -12497,7 +12497,7 @@ function NovaSelect({ if (opt.disabled) return; if (multi) { const exists = selectedSet.has(String(opt.value)); - onChange(exists ? selected.filter((v) => String(v) !== String(opt.value)) : [...selected, opt.value]); + onChange(exists ? selected.filter((v2) => String(v2) !== String(opt.value)) : [...selected, opt.value]); setSearch(""); searchRef.current?.focus(); } else { @@ -12512,9 +12512,9 @@ function NovaSelect({ }, [multi, onChange]); const removeTag = reactExports.useCallback((val, e) => { e.stopPropagation(); - onChange(selected.filter((v) => String(v) !== String(val))); + onChange(selected.filter((v2) => String(v2) !== String(val))); }, [selected, onChange]); - const handleKeyDown = reactExports.useCallback((e) => { + const handleKeyDown2 = reactExports.useCallback((e) => { if (!open) { if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) { e.preventDefault(); @@ -12571,23 +12571,23 @@ function NovaSelect({ tabIndex: disabled ? -1 : 0, className: triggerClass, onClick: open ? closeDropdown : openDropdown, - onKeyDown: handleKeyDown + onKeyDown: handleKeyDown2 }, - /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-1 flex-1 min-w-0" }, multi && selected.map((v) => /* @__PURE__ */ React.createElement( + /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-1 flex-1 min-w-0" }, multi && selected.map((v2) => /* @__PURE__ */ React.createElement( "span", { - key: v, + key: v2, className: "inline-flex items-center gap-1 h-6 px-2 rounded-md bg-accent/20 text-accent text-xs font-medium" }, - labelMap[String(v)] ?? v, + labelMap[String(v2)] ?? v2, /* @__PURE__ */ React.createElement( "button", { type: "button", tabIndex: -1, - onClick: (e) => removeTag(v, e), + onClick: (e) => removeTag(v2, e), className: "hover:text-white transition-colors", - "aria-label": `Remove ${labelMap[String(v)] ?? v}` + "aria-label": `Remove ${labelMap[String(v2)] ?? v2}` }, /* @__PURE__ */ React.createElement("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M1 1l6 6M7 1L1 7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })) ) @@ -12646,7 +12646,7 @@ function NovaSelect({ setSearch(e.target.value); setHigh(0); }, - onKeyDown: handleKeyDown, + onKeyDown: handleKeyDown2, placeholder: searchPlaceholder, className: "w-full pl-3 pr-7 py-1.5 rounded-lg bg-white/5 border border-white/8 text-white text-xs placeholder:text-slate-500 focus:outline-none focus:ring-1 focus:ring-accent/50", autoComplete: "off" @@ -12687,7 +12687,7 @@ function NovaSelect({ )); } function AcademyChallengeSubmit({ seo, challenge, artworks, submitUrl }) { - const form = G({ + const form = G$1({ artwork_id: artworks[0]?.id || "", prompt_used: "", workflow_notes: "", @@ -12696,7 +12696,7 @@ function AcademyChallengeSubmit({ seo, challenge, artworks, submitUrl }) { is_ai_assisted: true }); const artworkOptions = artworks.map((artwork) => ({ value: artwork.id, label: artwork.title })); - return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: `Submit to ${challenge.title}` }), /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: `Submit to ${challenge.title}`, description: challenge.excerpt || challenge.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[960px] space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Academy challenge"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Submit to ", challenge.title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-base leading-8 text-slate-300" }, "Attach one of your public artworks and optionally include the prompt or workflow notes used to create it.")), /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: `Submit to ${challenge.title}` }), /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: `Submit to ${challenge.title}`, description: challenge.excerpt || challenge.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[960px] space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Academy challenge"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Submit to ", challenge.title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-base leading-8 text-slate-300" }, "Attach one of your public artworks and optionally include the prompt or workflow notes used to create it.")), /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { event.preventDefault(); form.post(submitUrl); }, className: "space-y-5 rounded-[30px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: "text-sm font-semibold text-white" }, "Artwork"), /* @__PURE__ */ React.createElement( @@ -12714,13 +12714,179 @@ const __vite_glob_0_0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.def __proto__: null, default: AcademyChallengeSubmit }, Symbol.toStringTag, { value: "Module" })); +function CourseCard({ course, variant = "default" }) { + const isFeatured = variant === "featured"; + const progress = course?.progress || null; + const cover = course?.cover_image_url || course?.teaser_image_url || course?.cover_image || course?.teaser_image || ""; + return /* @__PURE__ */ React.createElement( + xe, + { + href: course.public_url, + className: [ + "group overflow-hidden rounded-[30px] border border-white/10 transition hover:border-sky-300/25 hover:bg-white/[0.06]", + isFeatured ? "bg-[linear-gradient(135deg,rgba(14,165,233,0.14),rgba(15,23,42,0.92))]" : "bg-white/[0.04]" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("div", { className: "relative" }, cover ? /* @__PURE__ */ React.createElement("img", { src: cover, alt: "", "aria-hidden": "true", className: `w-full object-cover ${isFeatured ? "h-56" : "h-44"}` }) : /* @__PURE__ */ React.createElement("div", { className: `w-full bg-[linear-gradient(135deg,rgba(14,165,233,0.22),rgba(15,23,42,0.92))] ${isFeatured ? "h-56" : "h-44"}` }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.82))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute left-5 top-5 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-100" }, course.difficulty), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-200" }, course.access_level), course.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/15 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-100" }, "Featured") : null)), + /* @__PURE__ */ React.createElement("div", { className: "p-6" }, /* @__PURE__ */ React.createElement("h2", { className: `font-semibold tracking-[-0.05em] text-white transition group-hover:text-sky-100 ${isFeatured ? "text-3xl" : "text-2xl"}` }, course.title), course.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-medium uppercase tracking-[0.18em] text-slate-400" }, course.subtitle) : null, /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, course.excerpt || course.description || "Structured Academy course."), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, course.lessons_count || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Duration"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, course.estimated_minutes ? `${course.estimated_minutes} min` : "Flexible")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Progress"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, progress ? `${progress.percent}%` : "Start fresh")))) + ); +} +function AcademyCoursesIndex({ seo, title, description, items, featuredCourses = [], filters = {}, pricingUrl }) { + const flash = X$1().props.flash || {}; + const difficultyOptions = [ + { value: "", label: "All levels" }, + { value: "beginner", label: "Beginner" }, + { value: "intermediate", label: "Intermediate" }, + { value: "advanced", label: "Advanced" } + ]; + const accessOptions = [ + { value: "", label: "All access" }, + { value: "free", label: "Free" }, + { value: "premium", label: "Premium" }, + { value: "mixed", label: "Mixed" } + ]; + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title, description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1400px] space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.96),rgba(14,165,233,0.12))] p-8 shadow-[0_24px_90px_rgba(2,6,23,0.36)] md:p-10 lg:p-12" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-6" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-4xl" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl lg:text-6xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 text-base leading-8 text-slate-300 md:text-lg" }, description)), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100" }, "See Academy plans"))), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, featuredCourses.length ? /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 xl:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement(CourseCard, { course: featuredCourses[0], variant: "featured" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5" }, featuredCourses.slice(1, 3).map((course) => /* @__PURE__ */ React.createElement(CourseCard, { key: course.id, course })))) : null, /* @__PURE__ */ React.createElement("section", { className: "grid gap-3 rounded-[30px] border border-white/10 bg-black/20 p-5 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( + NovaSelect, + { + label: "Difficulty", + value: filters?.difficulty || "", + onChange: (nextValue) => At.get(window.location.pathname, { ...filters, difficulty: nextValue || void 0 }, { preserveScroll: true, preserveState: true }), + options: difficultyOptions, + searchable: false, + className: "rounded-2xl bg-white/[0.04]" + } + ), /* @__PURE__ */ React.createElement( + NovaSelect, + { + label: "Access", + value: filters?.access || "", + onChange: (nextValue) => At.get(window.location.pathname, { ...filters, access: nextValue || void 0 }, { preserveScroll: true, preserveState: true }), + options: accessOptions, + searchable: false, + className: "rounded-2xl bg-white/[0.04]" + } + )), (items?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.04] px-6 py-12 text-center text-slate-400" }, "No published Academy courses matched these filters.") : /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, items.data.map((course) => /* @__PURE__ */ React.createElement(CourseCard, { key: course.id, course }))))); +} +const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: AcademyCoursesIndex +}, Symbol.toStringTag, { value: "Module" })); +function CourseBreadcrumbs({ items = [] }) { + if (!items.length) return null; + return /* @__PURE__ */ React.createElement("nav", { "aria-label": "Breadcrumb", className: "flex flex-wrap items-center gap-2 text-sm text-slate-400" }, items.map((item, index2) => { + const isLast = index2 === items.length - 1; + return /* @__PURE__ */ React.createElement(React.Fragment, { key: `${item.label}-${index2}` }, index2 > 0 ? /* @__PURE__ */ React.createElement("span", { className: "text-slate-600" }, "/") : null, isLast ? /* @__PURE__ */ React.createElement("span", { className: "font-medium text-slate-200" }, item.label) : /* @__PURE__ */ React.createElement(xe, { href: item.href, className: "transition hover:text-white" }, item.label)); + })); +} +function ProgressMeter({ progress }) { + const percent = Math.max(0, Math.min(100, Number(progress?.percent || 0))); + return /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.72))] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Progress"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-3xl font-semibold tracking-[-0.04em] text-white" }, percent, "%")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, progress ? "In progress" : "Not started")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 h-2.5 overflow-hidden rounded-full bg-white/[0.06]" }, /* @__PURE__ */ React.createElement( + "div", + { + className: "h-full rounded-full bg-[linear-gradient(90deg,rgba(125,211,252,0.95),rgba(251,191,36,0.9))] transition-[width] duration-500", + style: { width: `${percent}%` } + } + )), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, progress ? `${progress.completedRequired}/${progress.totalRequired} required lessons completed` : "Start the course to begin tracking progress through required lessons.")); +} +function LessonChip({ lesson }) { + const thumbnail = lesson?.cover_image_url || lesson?.article_cover_image_url || lesson?.cover_image || lesson?.article_cover_image || ""; + const stepLabel = lesson?.course_step_label || null; + const stepNumber = Number(lesson?.course_step_number || 0); + const isCompleted = Boolean(lesson?.completed); + const readingMinutes = Number(lesson?.reading_minutes || 0); + const ctaLabel = isCompleted ? "Review lesson" : "Open lesson"; + return /* @__PURE__ */ React.createElement( + xe, + { + href: lesson.course_url || `/academy/lessons/${lesson.slug}`, + className: [ + "group relative overflow-hidden rounded-[32px] border bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.64))] shadow-[0_24px_50px_rgba(2,6,23,0.2)] transition duration-200 hover:-translate-y-1 hover:border-sky-300/30 hover:shadow-[0_28px_70px_rgba(14,165,233,0.12)]", + isCompleted ? "border-emerald-300/25" : "border-white/10" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(125,211,252,0.09),transparent_24%),linear-gradient(135deg,transparent,rgba(255,255,255,0.02))] opacity-70 transition duration-200 group-hover:opacity-100" }), + /* @__PURE__ */ React.createElement("div", { className: "relative grid gap-0 lg:grid-cols-[172px_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "relative border-b border-white/10 bg-slate-950 lg:border-b-0 lg:border-r" }, thumbnail ? /* @__PURE__ */ React.createElement("img", { src: thumbnail, alt: "", "aria-hidden": "true", className: "h-40 w-full object-cover lg:h-full" }) : /* @__PURE__ */ React.createElement("div", { className: "h-40 w-full bg-[linear-gradient(135deg,rgba(56,189,248,0.18),rgba(15,23,42,0.92))] lg:h-full" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,rgba(2,6,23,0.04),rgba(2,6,23,0.84))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-3 top-3 flex items-start justify-between gap-3" }, lesson.is_required ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/35 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-white/80 backdrop-blur" }, "Required") : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/35 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-white/65 backdrop-blur" }, "Optional"), isCompleted ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-emerald-300/25 bg-emerald-300/12 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100 backdrop-blur" }, /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", className: "h-3.5 w-3.5" }, /* @__PURE__ */ React.createElement("circle", { cx: "8", cy: "8", r: "7", stroke: "currentColor", strokeWidth: "1.2" }), /* @__PURE__ */ React.createElement("path", { d: "M4.75 8.2 7 10.4l4.25-4.8", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })), "Done") : null), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-3 bottom-3 flex items-end justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, stepLabel ? /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.24em] text-sky-100/80" }, stepLabel) : null, stepNumber > 0 ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-5xl font-semibold tracking-[-0.1em] text-white" }, String(stepNumber).padStart(2, "0")) : null))), /* @__PURE__ */ React.createElement("div", { className: "p-5 md:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 xl:grid-cols-[minmax(0,1fr)_200px] xl:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2.5" }, stepLabel ? /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-sky-100" }, stepLabel) : null, lesson.formatted_lesson_number ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, lesson.formatted_lesson_number) : null, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, lesson.difficulty || "lesson"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, lesson.access_level || "free"), readingMinutes > 0 ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, readingMinutes, " min") : null), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 max-w-3xl text-[1.65rem] font-semibold tracking-[-0.05em] text-white transition group-hover:text-sky-100" }, lesson.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, isCompleted ? "You already finished this lesson." : "Follow this step next in the course path."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-7 text-slate-300" }, lesson.excerpt || lesson.content_preview || "Open this lesson inside the course.")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5" }, lesson.lesson_type || "article"), lesson.category_name ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5" }, lesson.category_name) : null)), /* @__PURE__ */ React.createElement("div", { className: "flex h-full flex-col justify-between gap-4 xl:border-l xl:border-white/10 xl:pl-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3 xl:grid-cols-1" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Status"), /* @__PURE__ */ React.createElement("p", { className: `mt-2 text-sm font-semibold ${isCompleted ? "text-emerald-100" : "text-white"}` }, isCompleted ? "Completed" : "Up next")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, lesson.access_level || "Free")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Read time"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, readingMinutes > 0 ? `${readingMinutes} min` : "Quick read"))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 xl:justify-end" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.16em] text-slate-500" }, "Continue path"), /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 transition group-hover:border-sky-300/35 group-hover:bg-sky-300/14 group-hover:text-white" }, ctaLabel, /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", className: "h-4 w-4" }, /* @__PURE__ */ React.createElement("path", { d: "M3.5 8h9m0 0-3.5-3.5M12.5 8l-3.5 3.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })))))))) + ); +} +function SectionBlock({ section, isActive = false }) { + if (!section?.is_visible) return null; + return /* @__PURE__ */ React.createElement("section", { className: `rounded-[32px] border p-6 transition md:p-7 ${isActive ? "border-sky-300/25 bg-[linear-gradient(180deg,rgba(14,165,233,0.08),rgba(255,255,255,0.04))] shadow-[0_22px_50px_rgba(14,165,233,0.08)]" : "border-white/10 bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Course section"), /* @__PURE__ */ React.createElement("span", { className: `rounded-full border px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] ${isActive ? "border-sky-300/20 bg-sky-300/12 text-sky-100" : "border-white/10 bg-black/20 text-slate-300"}` }, section.order_num + 1)), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, section.title), section.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, section.description) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-300" }, section.lessons?.length || 0, " lessons"), isActive ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/12 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Reading now") : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-6" }, (section.lessons || []).map((lesson) => /* @__PURE__ */ React.createElement(LessonChip, { key: lesson.course_lesson_id || lesson.id, lesson })))); +} +function AcademyCoursesShow({ seo, course, sections = [], unsectionedLessons = [], pricingUrl }) { + const flash = X$1().props.flash || {}; + const cover = course?.cover_image_url || course?.cover_image || course?.teaser_image_url || course?.teaser_image || ""; + const progress = course?.progress || null; + const sectionJumpItems = reactExports.useMemo( + () => [ + ...unsectionedLessons.length ? [{ id: "course-outline-core", label: "Core lessons", count: unsectionedLessons.length }] : [], + ...sections.filter((section) => section?.is_visible).map((section) => ({ id: `section-${section.id}`, label: section.title, count: (section.lessons || []).length })) + ], + [sections, unsectionedLessons] + ); + const [activeJumpId, setActiveJumpId] = reactExports.useState(sectionJumpItems[0]?.id || null); + const breadcrumbs = [ + { label: "Academy", href: "/academy" }, + { label: "Courses", href: "/academy/courses" }, + { label: course?.title || "Course", href: course?.public_url || "#" } + ]; + reactExports.useEffect(() => { + if (!sectionJumpItems.length || typeof window === "undefined" || typeof IntersectionObserver === "undefined") { + return void 0; + } + const observer = new IntersectionObserver( + (entries) => { + const visibleEntries = entries.filter((entry) => entry.isIntersecting).sort((left, right) => right.intersectionRatio - left.intersectionRatio); + if (!visibleEntries.length) return; + setActiveJumpId(visibleEntries[0].target.id); + }, + { + rootMargin: "-20% 0px -55% 0px", + threshold: [0.2, 0.45, 0.7] + } + ); + const elements = sectionJumpItems.map((item) => document.getElementById(item.id)).filter(Boolean); + elements.forEach((element2) => observer.observe(element2)); + return () => observer.disconnect(); + }, [sectionJumpItems]); + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: course?.title, description: course?.excerpt || course?.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1400px] space-y-6" }, flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-black/20 shadow-[0_24px_90px_rgba(2,6,23,0.34)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 xl:grid-cols-[minmax(0,1.2fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "relative overflow-hidden p-6 md:p-8 lg:p-10 xl:p-12" }, cover ? /* @__PURE__ */ React.createElement("img", { src: cover, alt: "", "aria-hidden": "true", className: "absolute inset-0 h-full w-full object-cover opacity-[0.18]" }) : null, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(125,211,252,0.18),_transparent_28%),radial-gradient(circle_at_78%_26%,_rgba(251,191,36,0.12),_transparent_20%),linear-gradient(135deg,_rgba(2,6,23,0.98),_rgba(15,23,42,0.85))]" }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 max-w-5xl" }, /* @__PURE__ */ React.createElement(CourseBreadcrumbs, { items: breadcrumbs }), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap items-center gap-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-100" }, "Academy course"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.06] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-300" }, course?.difficulty), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.06] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-300" }, course?.access_level), progress?.percent ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/12 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-emerald-100" }, progress.percent, "% complete") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React.createElement("h1", { className: "text-4xl font-semibold tracking-[-0.06em] text-white md:text-5xl lg:text-[3.75rem]" }, course?.title), course?.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm font-semibold uppercase tracking-[0.24em] text-amber-100/90" }, course.subtitle) : null, /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, course?.excerpt || course?.description), /* @__PURE__ */ React.createElement("div", { className: "mt-7 overflow-hidden rounded-[32px] border border-white/10 bg-slate-950/80 shadow-[0_24px_60px_rgba(2,6,23,0.32)]" }, cover ? /* @__PURE__ */ React.createElement("img", { src: cover, alt: "", "aria-hidden": "true", className: "w-full object-contain" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-[360px] items-center justify-center bg-[linear-gradient(135deg,rgba(56,189,248,0.18),rgba(15,23,42,0.92))] px-6 text-center text-sm text-slate-400" }, "No course cover image yet"))))), /* @__PURE__ */ React.createElement("aside", { className: "border-t border-white/10 bg-white/[0.03] p-6 xl:border-l xl:border-t-0 xl:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 xl:sticky xl:top-6" }, /* @__PURE__ */ React.createElement(ProgressMeter, { progress }), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Jump through the course"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, sectionJumpItems.length ? sectionJumpItems.map((item) => /* @__PURE__ */ React.createElement( + "a", + { + key: item.id, + href: `#${item.id}`, + onClick: () => setActiveJumpId(item.id), + className: `flex items-center justify-between rounded-2xl border px-4 py-3 text-sm transition ${activeJumpId === item.id ? "border-sky-300/25 bg-sky-300/12 text-white" : "border-white/10 bg-white/[0.03] text-slate-300 hover:border-white/20 hover:bg-white/[0.06]"}` + }, + /* @__PURE__ */ React.createElement("span", { className: "font-medium" }, item.label), + /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, item.count) + )) : /* @__PURE__ */ React.createElement("p", { className: "rounded-2xl border border-dashed border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-400" }, "No course outline items are available yet."))))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, unsectionedLessons.length ? /* @__PURE__ */ React.createElement( + SectionBlock, + { + section: { + order_num: -1, + title: "Core lessons", + description: "Lessons shown before the course branches into sections.", + is_visible: true, + lessons: unsectionedLessons + }, + isActive: activeJumpId === "course-outline-core" + } + ) : null, sections.filter((section) => section?.is_visible).map((section) => /* @__PURE__ */ React.createElement(SectionBlock, { key: section.id, section, isActive: activeJumpId === `section-${section.id}` }))))); +} +const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: AcademyCoursesShow +}, Symbol.toStringTag, { value: "Module" })); function academyHref$1(section, slug) { return `/academy/${section}/${encodeURIComponent(slug)}`; } function FeatureCard({ title, description, href, cta: cta2 }) { return /* @__PURE__ */ React.createElement(xe, { href, className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6 transition hover:border-white/20 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Academy"), /* @__PURE__ */ React.createElement("h2", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, description), /* @__PURE__ */ React.createElement("span", { className: "mt-5 inline-flex rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100" }, cta2)); } -function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredLessons, featuredPrompts, featuredChallenges }) { +function FeaturedCourseCard({ course }) { + const cover = course?.cover_image_url || course?.teaser_image_url || course?.cover_image || course?.teaser_image || ""; + return /* @__PURE__ */ React.createElement(xe, { href: course.public_url, className: "group overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.04] transition hover:border-sky-300/25 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("div", { className: "relative h-44 overflow-hidden bg-[linear-gradient(135deg,rgba(14,165,233,0.24),rgba(15,23,42,0.92))]" }, cover ? /* @__PURE__ */ React.createElement("img", { src: cover, alt: "", "aria-hidden": "true", className: "h-full w-full object-cover" }) : null, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.82))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute left-4 top-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, course.difficulty), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-200" }, course.access_level))), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("h3", { className: "text-2xl font-semibold tracking-[-0.04em] text-white transition group-hover:text-sky-100" }, course.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, course.excerpt || course.description || "Guided Academy course."), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, course.lessons_count || 0, " lessons · ", course.estimated_minutes ? `${course.estimated_minutes} min` : "Flexible duration"))); +} +function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredCourses, featuredLessons, featuredPrompts, featuredChallenges }) { const jsonLd = [{ "@context": "https://schema.org", "@type": "WebPage", @@ -12728,9 +12894,9 @@ function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredLes description: seo?.description, url: seo?.canonical }]; - return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(251,191,36,0.18),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(56,189,248,0.18),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: "Skinbase AI Academy", description: seo?.description, jsonLd }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1440px] space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.94),rgba(28,25,23,0.88)),radial-gradient(circle_at_top_right,rgba(251,191,36,0.2),transparent_26%)] p-8 shadow-[0_32px_100px_rgba(2,6,23,0.38)] md:p-10 lg:p-12" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-end" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-amber-200/80" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 max-w-4xl text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl xl:text-6xl" }, "Learn how to turn prompts into wallpapers, digital art, skins, covers, and visual worlds."), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, "Skinbase AI Academy is the creative learning hub for AI-assisted art on Skinbase. Start with free lessons, explore prompt templates, and unlock premium workflows later."), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: links.lessons, className: "rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-300/40 hover:bg-amber-300/18" }, "Browse lessons"), /* @__PURE__ */ React.createElement(xe, { href: links.prompts, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.07]" }, "Open prompt library"), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18" }, "See plans"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-300" }, "Launch status"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Challenges"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.challengesEnabled ? "Enabled" : "Disabled")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Badges"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.badgesEnabled ? "Enabled" : "Disabled")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Payments"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.paymentsEnabled ? "Preview only" : "Disabled")))))), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement(FeatureCard, { title: "Lessons", description: "Structured tutorials for prompt writing, cleanup workflows, AI ethics, and Skinbase-native publishing habits.", href: links.lessons, cta: "Open lessons" }), /* @__PURE__ */ React.createElement(FeatureCard, { title: "Prompt Library", description: "Discover reusable prompt templates, locked premium previews, and creator-focused visual workflows.", href: links.prompts, cta: "Explore prompts" }), /* @__PURE__ */ React.createElement(FeatureCard, { title: "Challenges", description: "Join Academy creative briefs and submit artworks once the challenge system is enabled for your account.", href: links.challenges, cta: "View challenges" })), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.lessonCount || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Prompts"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.promptCount || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Challenges"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.challengeCount || 0))), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Featured lessons"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredLessons || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("lessons", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, item.title)))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Featured prompts"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredPrompts || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("prompts", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, item.title)))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Current challenges"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredChallenges || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("challenges", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, item.title))))))); + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(251,191,36,0.18),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(56,189,248,0.18),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: "Skinbase AI Academy", description: seo?.description, jsonLd }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1440px] space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.94),rgba(28,25,23,0.88)),radial-gradient(circle_at_top_right,rgba(251,191,36,0.2),transparent_26%)] p-8 shadow-[0_32px_100px_rgba(2,6,23,0.38)] md:p-10 lg:p-12" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-end" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-amber-200/80" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 max-w-4xl text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl xl:text-6xl" }, "Learn how to turn prompts into wallpapers, digital art, skins, covers, and visual worlds."), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, "Skinbase AI Academy is the creative learning hub for AI-assisted art on Skinbase. Start with free lessons, explore prompt templates, and unlock premium workflows later."), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: links.courses, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18" }, "Browse courses"), /* @__PURE__ */ React.createElement(xe, { href: links.lessons, className: "rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-300/40 hover:bg-amber-300/18" }, "Browse lessons"), /* @__PURE__ */ React.createElement(xe, { href: links.prompts, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.07]" }, "Open prompt library"), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18" }, "See plans"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-300" }, "Launch status"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Challenges"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.challengesEnabled ? "Enabled" : "Disabled")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Badges"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.badgesEnabled ? "Enabled" : "Disabled")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", null, "Payments"), /* @__PURE__ */ React.createElement("span", null, featureFlags?.paymentsEnabled ? "Preview only" : "Disabled")))))), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement(FeatureCard, { title: "Courses", description: "Follow guided learning paths that stitch reusable Academy lessons into a clean progression with completion tracking.", href: links.courses, cta: "Browse courses" }), /* @__PURE__ */ React.createElement(FeatureCard, { title: "Lessons", description: "Structured tutorials for prompt writing, cleanup workflows, AI ethics, and Skinbase-native publishing habits.", href: links.lessons, cta: "Open lessons" }), /* @__PURE__ */ React.createElement(FeatureCard, { title: "Prompt Library", description: "Discover reusable prompt templates, locked premium previews, and creator-focused visual workflows.", href: links.prompts, cta: "Explore prompts" })), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 lg:grid-cols-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Courses"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.courseCount || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.lessonCount || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Prompts"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.promptCount || 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Challenges"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white" }, stats?.challengeCount || 0))), featuredCourses?.length ? /* @__PURE__ */ React.createElement("section", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Featured courses"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-3xl font-semibold tracking-[-0.045em] text-white" }, "Guided Academy paths")), /* @__PURE__ */ React.createElement(xe, { href: links.courses, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "All courses")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 xl:grid-cols-3" }, featuredCourses.slice(0, 3).map((course) => /* @__PURE__ */ React.createElement(FeaturedCourseCard, { key: course.id, course })))) : null, /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Featured lessons"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredLessons || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("lessons", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, item.lesson_label || "Featured lesson"), /* @__PURE__ */ React.createElement("span", { className: "mt-1 block" }, item.title))))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Featured prompts"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredPrompts || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("prompts", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, item.title)))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Current challenges"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (featuredChallenges || []).slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(xe, { key: item.id, href: academyHref$1("challenges", item.slug), className: "block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, item.title))))))); } -const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyIndex }, Symbol.toStringTag, { value: "Module" })); @@ -12792,14 +12958,26 @@ function itemHref$1(pageType, item) { if (pageType === "packs") return academyHref("packs", item.slug); return academyHref("challenges", item.slug); } +function PromptLibraryHero({ title, description, items, pricingUrl }) { + const featuredImages = (items || []).map((item) => item?.preview_image).filter(Boolean).slice(0, 3); + const primaryImage = featuredImages[0] || ""; + const supportingImages = featuredImages.slice(1, 3); + return /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[38px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.14),transparent_26%),radial-gradient(circle_at_bottom_right,rgba(255,207,191,0.16),transparent_26%),linear-gradient(135deg,rgba(4,9,18,0.98),rgba(15,23,42,0.92))] p-8 shadow-[0_28px_90px_rgba(2,6,23,0.28)] md:p-10 lg:p-12" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.15fr)_420px] xl:items-end" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-[#fff0ea]" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, "Prompt Library")), /* @__PURE__ */ React.createElement("h1", { className: "mt-5 max-w-4xl text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl xl:text-6xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, description), /* @__PURE__ */ React.createElement("div", { className: "mt-7 grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Visual-first"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "Preview prompt results before opening the detail page.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Reusable"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "Templates for wallpapers, covers, worlds, portraits, and more.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Comparison-ready"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "See which prompts include provider-specific notes and outputs."))), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Upgrade preview"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white/85" }, items?.length || 0, " prompts in view"))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, primaryImage ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.18)] aspect-[16/10]" }, /* @__PURE__ */ React.createElement("img", { src: primaryImage, alt: "", "aria-hidden": "true", className: "h-full w-full object-cover" })), supportingImages.length ? /* @__PURE__ */ React.createElement("div", { className: `grid gap-3 ${supportingImages.length === 1 ? "grid-cols-1" : "grid-cols-2"}` }, supportingImages.map((image2, index2) => /* @__PURE__ */ React.createElement("div", { key: `${image2}-${index2}`, className: "overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.18)] aspect-square" }, /* @__PURE__ */ React.createElement("img", { src: image2, alt: "", "aria-hidden": "true", className: "h-full w-full object-cover" })))) : null) : /* @__PURE__ */ React.createElement("div", { className: "col-span-2 flex aspect-[16/10] items-center justify-center rounded-[28px] border border-white/10 bg-[linear-gradient(135deg,rgba(56,189,248,0.12),rgba(17,24,39,0.92))] px-8 text-center text-sm font-semibold uppercase tracking-[0.24em] text-slate-300" }, "Prompt preview images will appear here")))); +} function AcademyCard({ pageType, item }) { - return /* @__PURE__ */ React.createElement(xe, { href: itemHref$1(pageType, item), className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 transition hover:border-white/20 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, pageType.slice(0, -1)), /* @__PURE__ */ React.createElement(LockBadge, { item })), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.04em] text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.excerpt || item.description || item.prompt_preview || item.content_preview || "No description yet."), pageType === "prompts" && item.tags?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.tags.slice(0, 4).join(" · ")) : null, pageType === "challenges" ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.status, " · ", item.submission_count ?? 0, " submissions") : null); + const lessonSeries = String(item?.series_name || "").trim(); + const promptPreviewImage = item?.preview_image || ""; + if (pageType === "prompts") { + return /* @__PURE__ */ React.createElement(xe, { href: itemHref$1(pageType, item), className: "group overflow-hidden rounded-[30px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(7,11,18,0.96))] shadow-[0_20px_50px_rgba(2,6,23,0.18)] transition hover:border-sky-300/25 hover:bg-[linear-gradient(180deg,rgba(15,23,42,0.96),rgba(10,15,26,0.98))]" }, /* @__PURE__ */ React.createElement("div", { className: "relative aspect-[16/11] overflow-hidden bg-[linear-gradient(135deg,rgba(56,189,248,0.18),rgba(17,24,39,0.94))]" }, promptPreviewImage ? /* @__PURE__ */ React.createElement("img", { src: promptPreviewImage, alt: "", "aria-hidden": "true", className: "h-full w-full object-cover transition duration-500 group-hover:scale-[1.04]" }) : null, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.72))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute left-4 top-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-[#fff0ea]" }, "Prompt template"), /* @__PURE__ */ React.createElement(LockBadge, { item })), /* @__PURE__ */ React.createElement("div", { className: "absolute bottom-4 left-4 right-4 flex flex-wrap items-end justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, item?.difficulty ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-white" }, item.difficulty) : null, item?.aspect_ratio ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-white" }, item.aspect_ratio) : null))), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, item?.category?.name || "Academy"), Array.isArray(item?.tool_notes) && item.tool_notes.length ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#ffcfbf]/15 bg-[#ffcfbf]/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-[#fff0ea]" }, item.tool_notes.length, " comparisons") : null), /* @__PURE__ */ React.createElement("h2", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white transition group-hover:text-sky-100" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.excerpt || item.description || item.prompt_preview || "No description yet."), item.tags?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.tags.slice(0, 4).join(" · ")) : null)); + } + return /* @__PURE__ */ React.createElement(xe, { href: itemHref$1(pageType, item), className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 transition hover:border-white/20 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, pageType.slice(0, -1)), /* @__PURE__ */ React.createElement(LockBadge, { item })), pageType === "lessons" && item?.formatted_lesson_number ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-amber-100" }, item.formatted_lesson_number), lessonSeries ? /* @__PURE__ */ React.createElement("span", { className: "text-xs font-medium uppercase tracking-[0.18em] text-slate-500" }, lessonSeries) : null) : null, /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.04em] text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.excerpt || item.description || item.prompt_preview || item.content_preview || "No description yet."), pageType === "lessons" && item.tags?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.tags.slice(0, 4).join(" · ")) : null, pageType === "prompts" && item.tags?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.tags.slice(0, 4).join(" · ")) : null, pageType === "challenges" ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.status, " · ", item.submission_count ?? 0, " submissions") : null); } function AcademyList({ pageType, title, description, seo, items, filters, categories, pricingUrl }) { - const flash = X().props.flash || {}; - return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title, description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1360px] space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-base leading-8 text-slate-300" }, description)), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Upgrade preview"))), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement(QueryFilters, { pageType, filters, categories }), (items?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.04] px-6 py-12 text-center text-slate-400" }, "Nothing matched this Academy view yet.") : /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, items.data.map((item) => /* @__PURE__ */ React.createElement(AcademyCard, { key: `${pageType}-${item.id}`, pageType, item }))))); + const flash = X$1().props.flash || {}; + const visibleItems = Array.isArray(items?.data) ? items.data : []; + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title, description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1360px] space-y-6" }, pageType === "prompts" ? /* @__PURE__ */ React.createElement(PromptLibraryHero, { title, description, items: visibleItems, pricingUrl }) : /* @__PURE__ */ React.createElement("section", { className: "rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-base leading-8 text-slate-300" }, description)), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Upgrade preview"))), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement(QueryFilters, { pageType, filters, categories }), visibleItems.length === 0 ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.04] px-6 py-12 text-center text-slate-400" }, "Nothing matched this Academy view yet.") : /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, visibleItems.map((item) => /* @__PURE__ */ React.createElement(AcademyCard, { key: `${pageType}-${item.id}`, pageType, item }))))); } -const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyList }, Symbol.toStringTag, { value: "Module" })); @@ -12809,10 +12987,17 @@ function PlanCard({ plan, paymentsEnabled }) { function AcademyPricing({ seo, plans, paymentsEnabled }) { return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#111827_0%,_#0f172a_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: "Skinbase AI Academy Pricing", description: seo?.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1320px] space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Plans"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Choose your AI Academy plan."), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-base leading-8 text-slate-300" }, "Start free, unlock Creator and Pro previews, and keep the billing flow disabled until Stripe and Cashier are introduced in the next phase.")), /* @__PURE__ */ React.createElement("section", { className: "grid gap-5 lg:grid-cols-3" }, plans.map((plan) => /* @__PURE__ */ React.createElement(PlanCard, { key: plan.name, plan, paymentsEnabled }))))); } -const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyPricing }, Symbol.toStringTag, { value: "Module" })); +function AcademyBreadcrumbs({ items = [] }) { + if (!items.length) return null; + return /* @__PURE__ */ React.createElement("nav", { "aria-label": "Breadcrumb", className: "flex flex-wrap items-center gap-2 text-sm text-slate-400" }, items.map((item, index2) => { + const isLast = index2 === items.length - 1; + return /* @__PURE__ */ React.createElement(React.Fragment, { key: `${item.label}-${index2}` }, index2 > 0 ? /* @__PURE__ */ React.createElement("span", { className: "text-slate-600" }, "/") : null, isLast ? /* @__PURE__ */ React.createElement("span", { className: "font-medium text-slate-200" }, item.label) : /* @__PURE__ */ React.createElement(xe, { href: item.href, className: "transition hover:text-white" }, item.label)); + })); +} function slugifyHeading(value, fallback = "section") { const normalized = String(value || "").toLowerCase().trim().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); return normalized || fallback; @@ -12833,10 +13018,26 @@ function StatPill$2({ label, value }) { function LessonInfoRow({ label, value }) { return /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4 rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, label), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, value)); } +function LessonNavCard({ direction, lesson }) { + if (!lesson) return null; + const eyebrow = direction === "previous" ? "Previous lesson" : "Next lesson"; + const alignClass = direction === "previous" ? "items-start text-left" : "items-end text-right"; + const href = lesson.course_url || `/academy/lessons/${lesson.slug}`; + return /* @__PURE__ */ React.createElement( + xe, + { + href, + "aria-label": `${eyebrow}: ${lesson.title}`, + className: `group flex min-h-full flex-col justify-between rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-sky-300/25 hover:bg-white/[0.06] ${alignClass}` + }, + /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, eyebrow), lesson.lesson_label ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs font-semibold uppercase tracking-[0.2em] text-amber-100" }, lesson.lesson_label) : null, /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white transition group-hover:text-sky-100" }, lesson.title)), + /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, lesson.excerpt || lesson.content_preview || "Open the next step in this Academy sequence.") + ); +} function LockedPanel({ pricingUrl, label }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-amber-300/20 bg-amber-300/10 p-6 text-amber-50" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-100/80" }, "Premium content"), /* @__PURE__ */ React.createElement("h2", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em]" }, "Unlock the full ", label, "."), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-amber-50/90" }, "This preview is visible, but the full Academy content stays server-side until your account has the required Creator or Pro access."), /* @__PURE__ */ React.createElement(xe, { href: pricingUrl, className: "mt-5 inline-flex rounded-full border border-amber-200/25 bg-white/10 px-5 py-3 text-sm font-semibold text-white" }, "See Academy plans")); } -function copyTextToClipboard(text2) { +function copyTextToClipboard$1(text2) { const source = String(text2 || ""); if (!source) return Promise.reject(new Error("Nothing to copy")); if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") { @@ -12859,7 +13060,7 @@ function copyTextToClipboard(text2) { } return Promise.reject(new Error("Clipboard unavailable")); } -function PromptCopyButton({ prompt }) { +function PromptCopyButton({ prompt, label = "Copy prompt" }) { const [status2, setStatus] = reactExports.useState("idle"); const resetTimerRef = reactExports.useRef(0); return /* @__PURE__ */ React.createElement( @@ -12867,7 +13068,7 @@ function PromptCopyButton({ prompt }) { { type: "button", onClick: () => { - copyTextToClipboard(prompt).then(() => setStatus("copied")).catch(() => setStatus("failed")).finally(() => { + copyTextToClipboard$1(prompt).then(() => setStatus("copied")).catch(() => setStatus("failed")).finally(() => { window.clearTimeout(resetTimerRef.current); resetTimerRef.current = window.setTimeout(() => setStatus("idle"), 1800); }); @@ -12876,9 +13077,71 @@ function PromptCopyButton({ prompt }) { "aria-label": "Copy prompt" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${status2 === "copied" ? "fa-check" : status2 === "failed" ? "fa-triangle-exclamation" : "fa-copy"}` }), - /* @__PURE__ */ React.createElement("span", null, status2 === "copied" ? "Copied" : status2 === "failed" ? "Copy failed" : "Copy prompt") + /* @__PURE__ */ React.createElement("span", null, status2 === "copied" ? "Copied" : status2 === "failed" ? "Copy failed" : label) ); } +function ImageLightbox({ gallery, onClose, onNavigate }) { + reactExports.useEffect(() => { + if (!gallery?.images?.length) return void 0; + const handleEscape = (event) => { + if (event.key === "Escape") { + onClose(); + return; + } + if (event.key === "ArrowLeft") { + onNavigate(-1); + return; + } + if (event.key === "ArrowRight") { + onNavigate(1); + } + }; + document.body.style.overflow = "hidden"; + window.addEventListener("keydown", handleEscape); + return () => { + document.body.style.overflow = ""; + window.removeEventListener("keydown", handleEscape); + }; + }, [gallery, onClose, onNavigate]); + const images = Array.isArray(gallery?.images) ? gallery.images : []; + const currentIndex = Math.max(0, Math.min(images.length - 1, Number(gallery?.index || 0))); + const currentImage = images[currentIndex]; + if (!currentImage?.src) return null; + return /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-[120] flex items-center justify-center bg-[#020611e6] p-4 backdrop-blur-md", onClick: onClose, role: "dialog", "aria-modal": "true", "aria-label": currentImage.alt || "Image preview" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onClose, className: "absolute right-4 top-4 inline-flex h-11 w-11 items-center justify-center rounded-full border border-white/10 bg-black/35 text-white transition hover:border-white/25 hover:bg-white/10", "aria-label": "Close image preview" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-xmark text-lg" })), images.length > 1 ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: (event) => { + event.stopPropagation(); + onNavigate(-1); + }, className: "absolute left-4 top-1/2 inline-flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full border border-white/10 bg-black/35 text-white transition hover:border-white/25 hover:bg-white/10", "aria-label": "Previous image" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chevron-left" })) : null, images.length > 1 ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: (event) => { + event.stopPropagation(); + onNavigate(1); + }, className: "absolute right-4 top-1/2 inline-flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full border border-white/10 bg-black/35 text-white transition hover:border-white/25 hover:bg-white/10", "aria-label": "Next image" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chevron-right" })) : null, /* @__PURE__ */ React.createElement("div", { className: "max-h-[92vh] max-w-[min(1400px,96vw)] overflow-hidden rounded-[30px] border border-white/10 bg-black/30 shadow-[0_30px_120px_rgba(0,0,0,0.5)]", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("img", { src: currentImage.src, alt: currentImage.alt || "", className: "max-h-[92vh] w-full object-contain" }), images.length > 1 ? /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4 border-t border-white/10 bg-black/35 px-5 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, currentImage.alt || `Image ${currentIndex + 1}`), /* @__PURE__ */ React.createElement("p", { className: "text-xs uppercase tracking-[0.18em] text-slate-400" }, `Image ${currentIndex + 1} of ${images.length}`)), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, images.map((image2, index2) => /* @__PURE__ */ React.createElement( + "button", + { + key: `${image2.src}-${index2}`, + type: "button", + onClick: () => onNavigate(index2 - currentIndex), + className: `h-2.5 w-2.5 rounded-full transition ${index2 === currentIndex ? "bg-white" : "bg-white/25 hover:bg-white/45"}`, + "aria-label": `Go to image ${index2 + 1}` + } + )))) : null)); +} +function PromptToolNoteCard({ note, index: index2, galleryIndex, onOpenImage }) { + if (!note || typeof note !== "object") return null; + const title = note.model_name || note.provider || `Comparison ${String(index2 + 1).padStart(2, "0")}`; + const subtitle = [note.provider, note.model_name].filter(Boolean).join(" · "); + const previewUrl = note.image_url || note.thumb_url || ""; + const hasContent = Boolean(note.notes || note.strengths || note.weaknesses || note.best_for || note.settings || previewUrl || note.score || subtitle); + if (!hasContent) return null; + return /* @__PURE__ */ React.createElement("article", { className: "rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(15,23,42,0.22))] p-5 shadow-[0_16px_40px_rgba(2,6,23,0.16)]" }, previewUrl ? /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => onOpenImage?.(galleryIndex), + className: "group mb-5 block w-full overflow-hidden rounded-[24px] border border-white/10 bg-slate-950 text-left transition hover:border-sky-300/25 focus:outline-none focus:ring-2 focus:ring-sky-300/35", + "aria-label": `Open comparison image for ${title}` + }, + /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement("img", { src: previewUrl, alt: title, loading: "lazy", className: "aspect-[4/3] w-full object-cover transition duration-500 group-hover:scale-[1.03]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-0 bottom-0 flex items-center justify-between gap-3 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.72))] px-4 py-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-semibold uppercase tracking-[0.2em] text-slate-100/90" }, "Click to zoom"), /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-9 w-9 items-center justify-center rounded-full border border-white/10 bg-black/25 text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-expand" })))) + ) : null, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-[#ffcfbf]" }, "AI comparison"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-xl font-semibold tracking-[-0.03em] text-white" }, title), subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, subtitle) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-end gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, String(index2 + 1).padStart(2, "0")), note.score ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-3 py-1 text-xs font-semibold text-[#fff0ea]" }, `Score ${note.score}/10`) : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-4" }, note.settings ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/25 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Generated in"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, note.settings)) : null, note.notes ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Overall notes"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, note.notes)) : null, note.best_for ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-sky-300/15 bg-sky-300/10 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Best for"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-100" }, note.best_for)) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, note.strengths ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-emerald-300/15 bg-emerald-300/10 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-emerald-100" }, "Strengths"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-100" }, note.strengths)) : null, note.weaknesses ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-amber-300/15 bg-amber-300/10 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Weaknesses"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-100" }, note.weaknesses)) : null))); +} function AiComparisonSection({ block }) { const payload = block?.payload || {}; const criteria = Array.isArray(payload.criteria) ? payload.criteria.filter(Boolean) : []; @@ -12895,38 +13158,76 @@ function AiComparisonSection({ block }) { return /* @__PURE__ */ React.createElement("article", { key: result.id || `${result.provider}-${result.model_name}-${result.sort_order || 0}`, className: "overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.04] shadow-[0_16px_40px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "aspect-video overflow-hidden bg-slate-950/80" }, imageUrl ? /* @__PURE__ */ React.createElement("img", { src: imageUrl, alt: altText, loading: "lazy", className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full items-center justify-center px-6 text-center text-sm text-slate-500" }, "No comparison image provided.")), /* @__PURE__ */ React.createElement("div", { className: "space-y-4 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-xl font-semibold tracking-[-0.03em] text-white" }, result.model_name || result.provider || "AI model"), result.provider ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, result.provider) : null), hasScore ? /* @__PURE__ */ React.createElement("div", { className: "rounded-full border border-[#ffb8aa]/20 bg-[#ffb8aa]/10 px-3 py-1 text-sm font-semibold text-[#ffe3dd]" }, `Skinbase score ${score}/10`) : null), result.settings ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[20px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Settings"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-300" }, result.settings)) : null, result.strengths ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-emerald-200/75" }, "Strengths"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, result.strengths)) : null, result.weaknesses ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-200/75" }, "Weaknesses"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-300" }, result.weaknesses)) : null, result.best_for ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/75" }, "Best for"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, result.best_for)) : null)); })) : null); } -function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, completeUrl, completed: initialCompleted, saveUrl, unsaveUrl, saved: initialSaved, submitUrl }) { - const flash = X().props.flash || {}; +function AcademyShow({ pageType, item, relatedLessons = [], relatedCourses = [], previousLesson = null, nextLesson = null, seo, pricingUrl, completeUrl, completed: initialCompleted, saveUrl, unsaveUrl, saved: initialSaved, submitUrl, courseContext = null }) { + const flash = X$1().props.flash || {}; const [completed, setCompleted] = reactExports.useState(Boolean(initialCompleted)); const [saved, setSaved] = reactExports.useState(Boolean(initialSaved)); const [tableOfContents, setTableOfContents] = reactExports.useState([]); const [activeHeadingId, setActiveHeadingId] = reactExports.useState(""); + const [lightboxGallery, setLightboxGallery] = reactExports.useState(null); const articleContentRef = reactExports.useRef(null); + const handledInitialHashRef = reactExports.useRef(false); const lessonCover = item?.cover_image_url || item?.cover_image || ""; + const articleCover = item?.article_cover_image_url || item?.article_cover_image || ""; const lessonCategory = item?.category?.name || "Academy"; + const lessonSeries = String(item?.series_name || "").trim() || lessonCategory; const lessonDifficulty = item?.difficulty || "Intermediate"; const lessonMinutes = formatLessonMinutes(item?.reading_minutes); const lessonUpdated = formatLessonDate(item?.published_at); const lessonBlocks = Array.isArray(item?.blocks) ? item.blocks : []; const relatedLessonList = Array.isArray(relatedLessons) ? relatedLessons : []; + const relatedCourseList = Array.isArray(relatedCourses) ? relatedCourses : []; + const courseOutline = Array.isArray(courseContext?.outline) ? courseContext.outline : []; const lessonSummary = item.excerpt || item.description || item.prompt_preview || item.content_preview || "A focused Academy lesson with practical guidance and examples."; + const lessonTags = Array.isArray(item?.tags) ? item.tags.filter(Boolean) : []; + const promptPreviewImage = item?.preview_image || ""; + const promptBody = item?.prompt || item?.prompt_preview || ""; + const promptComparisons = Array.isArray(item?.tool_notes) ? item.tool_notes.filter((note) => note && typeof note === "object" && note.active !== false && [ + note.provider, + note.model_name, + note.notes, + note.strengths, + note.weaknesses, + note.best_for, + note.image_path, + note.image_url, + note.thumb_path, + note.thumb_url, + note.settings, + note.score + ].some(Boolean)) : []; + const promptUsageNotes = String(item?.usage_notes || "").trim(); + const promptWorkflowNotes = String(item?.workflow_notes || "").trim(); + const promptHasFullAccess = Boolean(item?.prompt); + const promptModelsCovered = promptComparisons.map((note, index2) => note.model_name || note.provider || `Model ${index2 + 1}`); + const promptComparisonGalleryImages = promptComparisons.map((note, index2) => { + const src2 = note.image_url || note.thumb_url || ""; + if (!src2) return null; + return { + src: src2, + alt: note.model_name || note.provider || `Comparison ${index2 + 1}` + }; + }).filter(Boolean); + const academyBreadcrumbs = pageType === "prompt" ? [ + { label: "Academy", href: "/academy" }, + { label: "Prompt Library", href: "/academy/prompts" }, + { label: item?.title || "Prompt" } + ] : []; const fontScaleStorageKey = "academy.lesson.font-scale"; const fontScaleMin = 0.95; const fontScaleMax = 1.12; const fontScaleStep = 0.04; - const [lessonFontScale, setLessonFontScale] = reactExports.useState(() => { - if (typeof window === "undefined") { - return 1.04; + const [lessonFontScale, setLessonFontScale] = reactExports.useState(1.04); + const findArticleHeading = (headingId) => { + if (!headingId || typeof document === "undefined") { + return null; } - const storedValue = Number(window.localStorage.getItem(fontScaleStorageKey)); - if (Number.isFinite(storedValue)) { - return Math.min(fontScaleMax, Math.max(fontScaleMin, storedValue)); - } - return 1.04; - }); + const escapedHeadingId = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(headingId) : String(headingId).replace(/[^a-zA-Z0-9_-]/g, ""); + return articleContentRef.current?.querySelector(`#${escapedHeadingId}`) || document.getElementById(headingId); + }; const markComplete = () => { if (!completeUrl || completed) return; - At.post(completeUrl, {}, { + At.post(completeUrl, courseContext?.completePayload || {}, { preserveScroll: true, onSuccess: () => setCompleted(true) }); @@ -12945,6 +13246,49 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com const increaseFontSize = () => { setLessonFontScale((current) => Math.min(fontScaleMax, Number((current + fontScaleStep).toFixed(2)))); }; + const openPromptPreviewImage = () => { + if (!promptPreviewImage) return; + setLightboxGallery({ + images: [{ src: promptPreviewImage, alt: item?.title || "Prompt preview" }], + index: 0 + }); + }; + const openPromptComparisonGallery = (index2) => { + if (!promptComparisonGalleryImages.length) return; + setLightboxGallery({ + images: promptComparisonGalleryImages, + index: Math.max(0, Math.min(promptComparisonGalleryImages.length - 1, Number(index2 || 0))) + }); + }; + const navigateLightboxGallery = (direction) => { + setLightboxGallery((current) => { + if (!current?.images?.length) return current; + const total = current.images.length; + const nextIndex = typeof direction === "number" && Math.abs(direction) > 1 ? Math.max(0, Math.min(total - 1, current.index + direction)) : (current.index + direction + total) % total; + return { + ...current, + index: nextIndex + }; + }); + }; + const scrollToHeading = (headingId, behavior = "smooth") => { + if (typeof window === "undefined") { + return; + } + const heading2 = findArticleHeading(headingId); + if (!heading2) { + return; + } + const top = Math.max(0, window.scrollY + heading2.getBoundingClientRect().top - 112); + window.scrollTo({ top, behavior }); + setActiveHeadingId(headingId); + if (window.history?.replaceState) { + window.history.replaceState(null, "", `${window.location.pathname}${window.location.search}#${headingId}`); + } + }; + reactExports.useEffect(() => { + handledInitialHashRef.current = false; + }, [item?.slug]); reactExports.useEffect(() => { if (pageType !== "lesson" || !item?.content || !articleContentRef.current) { setTableOfContents([]); @@ -12959,6 +13303,7 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com const nextId = seenCount > 0 ? `${baseId}-${seenCount + 1}` : baseId; seenIds.set(baseId, seenCount + 1); heading2.id = nextId; + heading2.style.scrollMarginTop = "128px"; return { id: nextId, title: heading2.textContent?.trim() || `Section ${index2 + 1}`, @@ -12967,33 +13312,73 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com }); setTableOfContents(nextTableOfContents); }, [item?.content, pageType]); + reactExports.useEffect(() => { + if (pageType !== "lesson" || tableOfContents.length === 0 || handledInitialHashRef.current || typeof window === "undefined") { + return; + } + const hash = window.location.hash.replace(/^#/, "").trim(); + if (!hash) { + handledInitialHashRef.current = true; + return; + } + const matchingEntry = tableOfContents.find((entry) => entry.id === hash); + if (!matchingEntry) { + handledInitialHashRef.current = true; + return; + } + handledInitialHashRef.current = true; + window.requestAnimationFrame(() => scrollToHeading(matchingEntry.id, "auto")); + }, [pageType, tableOfContents]); + reactExports.useEffect(() => { + if (pageType !== "lesson" || tableOfContents.length === 0 || typeof window === "undefined") { + return void 0; + } + const handleHashChange = () => { + const hash = window.location.hash.replace(/^#/, "").trim(); + if (!hash) { + return; + } + const matchingEntry = tableOfContents.find((entry) => entry.id === hash); + if (!matchingEntry) { + return; + } + window.requestAnimationFrame(() => scrollToHeading(matchingEntry.id, "auto")); + }; + window.addEventListener("hashchange", handleHashChange); + return () => window.removeEventListener("hashchange", handleHashChange); + }, [pageType, tableOfContents]); reactExports.useEffect(() => { if (pageType !== "lesson" || tableOfContents.length === 0 || !articleContentRef.current) { setActiveHeadingId(""); return; } - const headingElements = Array.from(articleContentRef.current.querySelectorAll("h2, h3")); - if (!headingElements.length) { - setActiveHeadingId(""); + const getActiveId = () => { + const headings = Array.from(articleContentRef.current.querySelectorAll("h2[id], h3[id]")); + if (!headings.length) return ""; + const offset = 140; + let activeId = headings[0].id; + for (const heading2 of headings) { + if (heading2.getBoundingClientRect().top <= offset) { + activeId = heading2.id; + } + } + return activeId; + }; + setActiveHeadingId(getActiveId()); + const onScroll = () => setActiveHeadingId(getActiveId()); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, [pageType, tableOfContents, lessonFontScale]); + reactExports.useEffect(() => { + if (typeof window === "undefined") { return; } - const observer = new IntersectionObserver((entries) => { - const visibleEntries = entries.filter((entry) => entry.isIntersecting).sort((left, right) => left.boundingClientRect.top - right.boundingClientRect.top); - if (visibleEntries.length) { - setActiveHeadingId((current) => visibleEntries[0].target.id || current); - } - }, { - root: null, - rootMargin: "-18% 0px -68% 0px", - threshold: [0, 1] - }); - headingElements.forEach((heading2) => observer.observe(heading2)); - const firstVisibleHeading = headingElements.find((heading2) => heading2.getBoundingClientRect().top >= 0) || headingElements[0]; - if (firstVisibleHeading?.id) { - setActiveHeadingId(firstVisibleHeading.id); + const storedValue = Number(window.localStorage.getItem(fontScaleStorageKey)); + if (!Number.isFinite(storedValue)) { + return; } - return () => observer.disconnect(); - }, [pageType, tableOfContents, lessonFontScale]); + setLessonFontScale(Math.min(fontScaleMax, Math.max(fontScaleMin, storedValue))); + }, [fontScaleMax, fontScaleMin, fontScaleStorageKey]); reactExports.useEffect(() => { if (typeof window === "undefined") { return; @@ -13074,7 +13459,7 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com pre.dataset.academyCopyButtonMounted = "true"; }); }, [item?.content, lessonFontScale, pageType]); - return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: item?.title, description: item?.excerpt || item?.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1320px] space-y-6" }, flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, item.locked ? /* @__PURE__ */ React.createElement(LockedPanel, { pricingUrl, label: pageType }) : null, pageType === "lesson" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-black/20 shadow-[0_24px_90px_rgba(15,23,42,0.34)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 lg:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "relative overflow-hidden p-8 md:p-10 lg:p-12" }, lessonCover ? /* @__PURE__ */ React.createElement("img", { src: lessonCover, alt: "", "aria-hidden": "true", className: "absolute inset-0 h-full w-full object-cover opacity-15" }) : null, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.22),_transparent_34%),linear-gradient(135deg,_rgba(2,6,23,0.96),_rgba(15,23,42,0.78))]" }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 max-w-3xl" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-100" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonCategory), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonDifficulty)), /* @__PURE__ */ React.createElement("h1", { className: "mt-5 text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl lg:text-6xl" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-2xl text-base leading-8 text-slate-300 md:text-lg" }, lessonSummary), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, completeUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: markComplete, className: "rounded-full border border-emerald-300/25 bg-emerald-300/12 px-5 py-3 text-sm font-semibold text-emerald-100" }, completed ? "Completed" : "Mark complete") : null, saveUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: toggleSave, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, saved ? "Saved" : "Save prompt") : null, submitUrl ? /* @__PURE__ */ React.createElement(xe, { href: submitUrl, className: "rounded-full border border-white/10 bg-white/[0.06] px-5 py-3 text-sm font-semibold text-white transition hover:border-sky-300/25 hover:bg-sky-300/12 hover:text-sky-100" }, "Submit artwork") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-3 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatPill$2, { label: "Category", value: lessonCategory }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Reading", value: lessonMinutes }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Updated", value: lessonUpdated }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Access", value: item.access_level || "free" })))), /* @__PURE__ */ React.createElement("aside", { className: "border-t border-white/10 bg-white/[0.03] p-6 lg:border-l lg:border-t-0 lg:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-5 lg:sticky lg:top-6" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-black/20" }, lessonCover ? /* @__PURE__ */ React.createElement("img", { src: lessonCover, alt: item.title, className: "h-52 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-52 items-center justify-center bg-[linear-gradient(135deg,_rgba(14,165,233,0.18),_rgba(17,24,39,0.94))] text-sm font-semibold uppercase tracking-[0.24em] text-slate-300" }, "Lesson cover")), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Series", value: lessonCategory }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Difficulty", value: lessonDifficulty }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Reading time", value: lessonMinutes }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Published", value: lessonUpdated })), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Lesson status"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.locked ? "This lesson is partially locked for your account level." : "Full lesson content is available below.")))))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 lg:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("article", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 text-slate-200 md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4 border-b border-white/10 pb-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Article"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "Lesson content")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-slate-300" }, lessonMinutes), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 rounded-full border border-white/10 bg-black/20 p-1" }, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(59,130,246,0.14),_transparent_26%),linear-gradient(180deg,_#0b1220_0%,_#111827_46%,_#0f172a_100%)] px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: seo || {}, title: item?.title, description: item?.excerpt || item?.description }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-[1320px] space-y-6" }, flash.success ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, item.locked ? /* @__PURE__ */ React.createElement(LockedPanel, { pricingUrl, label: pageType }) : null, pageType === "lesson" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-black/20 shadow-[0_24px_90px_rgba(15,23,42,0.34)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 lg:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "relative overflow-hidden p-8 md:p-10 lg:p-12" }, lessonCover ? /* @__PURE__ */ React.createElement("img", { src: lessonCover, alt: "", "aria-hidden": "true", className: "absolute inset-0 h-full w-full object-cover opacity-15" }) : null, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.22),_transparent_34%),linear-gradient(135deg,_rgba(2,6,23,0.96),_rgba(15,23,42,0.78))]" }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 max-w-3xl" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-100" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonCategory), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonDifficulty)), item.lesson_label ? /* @__PURE__ */ React.createElement("p", { className: "mt-5 text-sm font-semibold uppercase tracking-[0.24em] text-amber-100" }, item.lesson_label) : null, /* @__PURE__ */ React.createElement("h1", { className: "mt-5 text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl lg:text-6xl" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-2xl text-base leading-8 text-slate-300 md:text-lg" }, lessonSummary), lessonTags.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-2" }, lessonTags.map((tag) => /* @__PURE__ */ React.createElement("span", { key: tag, className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-200" }, tag))) : null, courseContext?.title ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 max-w-2xl rounded-[24px] border border-white/10 bg-black/25 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Part of course"), /* @__PURE__ */ React.createElement(xe, { href: courseContext.showUrl, className: "mt-2 inline-flex text-lg font-semibold text-sky-100 transition hover:text-white" }, courseContext.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, courseContext.subtitle || "This lesson is being viewed inside a structured Academy course path.")) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, completeUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: markComplete, className: "rounded-full border border-emerald-300/25 bg-emerald-300/12 px-5 py-3 text-sm font-semibold text-emerald-100" }, completed ? "Completed" : "Mark complete") : null, saveUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: toggleSave, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, saved ? "Saved" : "Save prompt") : null, submitUrl ? /* @__PURE__ */ React.createElement(xe, { href: submitUrl, className: "rounded-full border border-white/10 bg-white/[0.06] px-5 py-3 text-sm font-semibold text-white transition hover:border-sky-300/25 hover:bg-sky-300/12 hover:text-sky-100" }, "Submit artwork") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-3 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatPill$2, { label: "Category", value: lessonCategory }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Reading", value: lessonMinutes }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Updated", value: lessonUpdated }), /* @__PURE__ */ React.createElement(StatPill$2, { label: courseContext?.title ? "Course progress" : "Access", value: courseContext?.progress ? `${courseContext.progress.percent}%` : item.access_level || "free" })))), /* @__PURE__ */ React.createElement("aside", { className: "border-t border-white/10 bg-white/[0.03] p-6 lg:border-l lg:border-t-0 lg:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-5 lg:sticky lg:top-6" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-black/20" }, lessonCover ? /* @__PURE__ */ React.createElement("img", { src: lessonCover, alt: item.title, className: "h-52 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-52 items-center justify-center bg-[linear-gradient(135deg,_rgba(14,165,233,0.18),_rgba(17,24,39,0.94))] text-sm font-semibold uppercase tracking-[0.24em] text-slate-300" }, "Lesson cover")), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Series", value: lessonSeries }), item.formatted_lesson_number ? /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Lesson", value: item.formatted_lesson_number }) : null, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Difficulty", value: lessonDifficulty }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Reading time", value: lessonMinutes }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Published", value: lessonUpdated })), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Lesson status"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.locked ? "This lesson is partially locked for your account level." : courseContext?.title ? "This lesson is being tracked inside a course. Completion updates your course progress." : "Full lesson content is available below.")))))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 lg:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("article", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.045),rgba(148,163,184,0.03))] p-6 text-slate-200 shadow-[0_24px_70px_rgba(2,6,23,0.2)] md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4 border-b border-white/10 pb-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Article"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "Lesson content")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-slate-300" }, lessonMinutes), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 rounded-full border border-white/10 bg-black/20 p-1" }, /* @__PURE__ */ React.createElement( "button", { type: "button", @@ -13094,7 +13479,7 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com className: "inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/10 bg-white/[0.04] text-sm font-semibold text-slate-200 transition hover:border-sky-300/30 hover:bg-sky-300/12 hover:text-sky-100 disabled:cursor-not-allowed disabled:opacity-40" }, "+" - )))), /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, item.content ? /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement( + )))), /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, articleCover ? /* @__PURE__ */ React.createElement("div", { className: "mb-8 overflow-hidden rounded-[28px] border border-white/10 bg-black/20" }, /* @__PURE__ */ React.createElement("img", { src: articleCover, alt: `${item.title} article cover`, className: "w-full object-cover" })) : null, item.content ? /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement( "div", { ref: articleContentRef, @@ -13102,28 +13487,42 @@ function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, com style: { "--academy-lesson-font-scale": lessonFontScale }, dangerouslySetInnerHTML: { __html: item.content } } - ), lessonBlocks.map((block) => /* @__PURE__ */ React.createElement(AiComparisonSection, { key: block.id || `${block.type}-${block.sort_order || 0}`, block }))) : /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("div", { className: "whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.content_preview), lessonBlocks.map((block) => /* @__PURE__ */ React.createElement(AiComparisonSection, { key: block.id || `${block.type}-${block.sort_order || 0}`, block }))))), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6 lg:sticky lg:top-6 lg:self-start" }, tableOfContents.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 text-slate-200" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "On this page"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "Table of contents"), /* @__PURE__ */ React.createElement("nav", { "aria-label": "Lesson table of contents", className: "mt-5 space-y-1.5" }, tableOfContents.map((entry) => /* @__PURE__ */ React.createElement( + ), lessonBlocks.map((block) => /* @__PURE__ */ React.createElement(AiComparisonSection, { key: block.id || `${block.type}-${block.sort_order || 0}`, block }))) : /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("div", { className: "whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.content_preview), lessonBlocks.map((block) => /* @__PURE__ */ React.createElement(AiComparisonSection, { key: block.id || `${block.type}-${block.sort_order || 0}`, block })))), previousLesson || nextLesson ? /* @__PURE__ */ React.createElement("section", { className: "mt-10 border-t border-white/10 pt-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, courseContext?.title ? "Course navigation" : "Lesson navigation"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, courseContext?.title ? "Continue this course" : "Continue in order"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(LessonNavCard, { direction: "previous", lesson: previousLesson }), /* @__PURE__ */ React.createElement(LessonNavCard, { direction: "next", lesson: nextLesson }))) : null), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6 lg:sticky lg:top-6 lg:self-start" }, tableOfContents.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "On this page"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "Table of contents"), /* @__PURE__ */ React.createElement("nav", { "aria-label": "Lesson table of contents", className: "mt-5 space-y-1.5" }, tableOfContents.map((entry) => /* @__PURE__ */ React.createElement( "a", { key: entry.id, href: `#${entry.id}`, + onClick: (event) => { + event.preventDefault(); + scrollToHeading(entry.id); + }, "aria-current": activeHeadingId === entry.id ? "location" : void 0, className: `academy-lesson-toc-link ${entry.level === "h3" ? "academy-lesson-toc-link-subtle" : ""} ${activeHeadingId === entry.id ? "academy-lesson-toc-link-active" : ""}` }, /* @__PURE__ */ React.createElement("span", { className: "academy-lesson-toc-link-indicator", "aria-hidden": "true" }), /* @__PURE__ */ React.createElement("span", null, entry.title) - )))) : null, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 text-slate-200" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Series info"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, lessonCategory), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Category", value: lessonCategory }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Difficulty", value: lessonDifficulty }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Reading", value: lessonMinutes }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Updated", value: lessonUpdated }))), relatedLessonList.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 text-slate-200" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Continue learning"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "More in ", lessonCategory), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, relatedLessonList.map((relatedLesson, index2) => /* @__PURE__ */ React.createElement( + )))) : null, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, courseContext?.title ? "Course progress" : "Series info"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, courseContext?.title ? courseContext.title : lessonSeries), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Category", value: lessonCategory }), item.formatted_lesson_number ? /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Lesson", value: item.formatted_lesson_number }) : null, /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Difficulty", value: lessonDifficulty }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: "Reading", value: lessonMinutes }), /* @__PURE__ */ React.createElement(LessonInfoRow, { label: courseContext?.title ? "Progress" : "Updated", value: courseContext?.progress ? `${courseContext.progress.completedRequired}/${courseContext.progress.totalRequired} completed` : lessonUpdated }))), courseOutline.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Course outline"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, courseOutline.map((outlineLesson, index2) => /* @__PURE__ */ React.createElement(xe, { key: outlineLesson.course_lesson_id || outlineLesson.id || index2, href: outlineLesson.course_url || `/academy/lessons/${outlineLesson.slug}`, className: `flex items-start gap-3 rounded-[20px] border px-4 py-3 text-sm transition ${outlineLesson.slug === item.slug ? "border-sky-300/25 bg-sky-300/10 text-sky-100" : "border-white/10 bg-black/20 text-slate-300 hover:border-sky-300/25 hover:bg-white/[0.06]"}` }, /* @__PURE__ */ React.createElement("span", { className: "mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-white/10 bg-white/[0.05] text-[10px] font-semibold" }, String(index2 + 1).padStart(2, "0")), /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("span", { className: "block font-semibold" }, outlineLesson.title), /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-xs uppercase tracking-[0.16em] text-slate-500" }, outlineLesson.is_required ? "Required" : "Optional")))))) : null, lessonTags.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Microtags"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, lessonTags.map((tag) => /* @__PURE__ */ React.createElement("span", { key: tag, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-sky-100" }, tag)))) : null, relatedLessonList.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Continue learning"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "More in ", lessonSeries), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, relatedLessonList.map((relatedLesson, index2) => /* @__PURE__ */ React.createElement( xe, { key: relatedLesson.id, - href: `/academy/lessons/${relatedLesson.slug}`, + href: relatedLesson.course_url || `/academy/lessons/${relatedLesson.slug}`, className: "group flex gap-4 rounded-[22px] border border-white/10 bg-black/20 p-4 transition hover:border-sky-300/25 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-sky-300/15 bg-sky-300/10 text-sm font-semibold text-sky-100" }, String(index2 + 1).padStart(2, "0")), - /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white transition group-hover:text-sky-100" }, relatedLesson.title), /* @__PURE__ */ React.createElement("span", { className: "shrink-0 rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, formatLessonMinutes(relatedLesson.reading_minutes))), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-6 text-slate-400" }, relatedLesson.excerpt || relatedLesson.content_preview || "Continue the series with the next lesson.")) - )))) : null))) : /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.04] p-6 text-slate-200" }, pageType === "prompt" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Prompt"), /* @__PURE__ */ React.createElement("pre", { className: "mt-3 whitespace-pre-wrap rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-200" }, item.prompt || item.prompt_preview)), item.negative_prompt ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Negative prompt"), /* @__PURE__ */ React.createElement("pre", { className: "mt-3 whitespace-pre-wrap rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-200" }, item.negative_prompt)) : null) : null, pageType === "pack" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-8 text-slate-200" }, item.description), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, (item.prompts || []).map((prompt) => /* @__PURE__ */ React.createElement("div", { key: prompt.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, prompt.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, prompt.excerpt || prompt.prompt_preview))))) : null, pageType === "challenge" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Brief"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.brief || item.description)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Rules"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.rules || "No special rules posted yet."))), (item.submissions || []).length ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Approved submissions"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, item.submissions.map((submission) => /* @__PURE__ */ React.createElement("div", { key: submission.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, submission.artwork?.title || "Submission"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, submission.user?.name || "Unknown creator"))))) : null) : null))); + /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, relatedLesson.formatted_lesson_number ? /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, relatedLesson.formatted_lesson_number) : null, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white transition group-hover:text-sky-100" }, relatedLesson.title)), /* @__PURE__ */ React.createElement("span", { className: "shrink-0 rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, formatLessonMinutes(relatedLesson.reading_minutes))), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-6 text-slate-400" }, relatedLesson.excerpt || relatedLesson.content_preview || "Continue the series with the next lesson.")) + )))) : null, relatedCourseList.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Related courses"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, relatedCourseList.map((course) => /* @__PURE__ */ React.createElement(xe, { key: course.id, href: course.public_url, className: "block rounded-[22px] border border-white/10 bg-black/20 p-4 transition hover:border-sky-300/25 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, course.difficulty, " · ", course.access_level), /* @__PURE__ */ React.createElement("h4", { className: "mt-2 text-sm font-semibold text-white" }, course.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-6 text-slate-400" }, course.excerpt || course.description || "Open this course to continue with a guided path."))))) : null))) : pageType === "prompt" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(4,10,20,0.98),rgba(15,23,42,0.9))] shadow-[0_24px_90px_rgba(15,23,42,0.34)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 lg:grid-cols-[minmax(420px,0.92fr)_minmax(0,1.08fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "relative border-b border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.18),transparent_34%),radial-gradient(circle_at_bottom_right,rgba(255,183,139,0.18),transparent_32%),linear-gradient(180deg,rgba(5,10,20,0.98),rgba(10,17,30,0.94))] p-6 md:p-8 lg:min-h-[760px] lg:border-b-0 lg:border-r lg:border-white/10 lg:p-10" }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(125,211,252,0.12),transparent_24%),radial-gradient(circle_at_80%_75%,rgba(255,207,191,0.12),transparent_28%)]" }), /* @__PURE__ */ React.createElement("div", { className: "relative flex h-full flex-col" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-[#ffd8cd]" }, "Preview artwork"), promptPreviewImage ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "Click to zoom") : null), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: openPromptPreviewImage, + className: "group mt-4 flex-1 overflow-hidden rounded-[32px] border border-white/10 bg-black/30 text-left shadow-[0_24px_80px_rgba(2,6,23,0.26)] transition hover:border-sky-300/25 focus:outline-none focus:ring-2 focus:ring-sky-300/35", + disabled: !promptPreviewImage, + "aria-label": promptPreviewImage ? `Open preview image for ${item.title}` : "Preview image unavailable" + }, + promptPreviewImage ? /* @__PURE__ */ React.createElement("div", { className: "relative h-full min-h-[360px] overflow-hidden lg:min-h-[620px]" }, /* @__PURE__ */ React.createElement("img", { src: promptPreviewImage, alt: item.title, className: "h-full w-full object-cover transition duration-500 group-hover:scale-[1.03]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,rgba(2,6,23,0.02),rgba(2,6,23,0.28))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute bottom-4 left-4 right-4 flex items-end justify-between gap-4 rounded-[24px] border border-white/10 bg-black/25 px-4 py-3 backdrop-blur-md" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-sky-100/80" }, "Prompt visual"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm font-semibold text-white" }, "Open full-size preview")), /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/10 bg-white/10 text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-expand" })))) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full min-h-[360px] items-center justify-center bg-[linear-gradient(135deg,rgba(251,146,60,0.14),rgba(17,24,39,0.96))] px-8 text-center lg:min-h-[620px]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Visual placeholder"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-lg font-semibold text-white" }, "Preview image coming soon"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, "This prompt page will feel much better once the generated cover image is attached."))) + ))), /* @__PURE__ */ React.createElement("div", { className: "relative overflow-hidden p-8 md:p-10 lg:p-12" }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,183,139,0.14),_transparent_28%),radial-gradient(circle_at_bottom_right,_rgba(56,189,248,0.12),_transparent_28%)]" }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 max-w-4xl" }, academyBreadcrumbs.length ? /* @__PURE__ */ React.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React.createElement(AcademyBreadcrumbs, { items: academyBreadcrumbs })) : null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-[#fff0ea]" }, "Skinbase AI Academy"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonCategory), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, lessonDifficulty), item.aspect_ratio ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, item.aspect_ratio) : null, item.prompt_of_week ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/25 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-amber-100" }, "Prompt of the week") : null, item.featured ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-100" }, "Featured") : null), /* @__PURE__ */ React.createElement("p", { className: "mt-8 text-sm font-semibold uppercase tracking-[0.24em] text-[#ffd8cd]" }, "Prompt template"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 max-w-4xl text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl xl:text-6xl" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, lessonSummary), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, saveUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: toggleSave, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, saved ? "Saved" : "Save prompt") : null, promptBody ? /* @__PURE__ */ React.createElement(PromptCopyButton, { prompt: promptBody }) : null, item.negative_prompt ? /* @__PURE__ */ React.createElement(PromptCopyButton, { prompt: item.negative_prompt, label: "Copy negative" }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-3 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatPill$2, { label: "Category", value: lessonCategory }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Access", value: item.access_level || "free" }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Difficulty", value: lessonDifficulty }), /* @__PURE__ */ React.createElement(StatPill$2, { label: "Updated", value: lessonUpdated })), lessonTags.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-8 rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.045),rgba(148,163,184,0.03))] p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Microtags"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, lessonTags.map((tag) => /* @__PURE__ */ React.createElement("span", { key: tag, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-sky-100" }, tag)))) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-4 xl:grid-cols-[minmax(0,1.05fr)_minmax(280px,0.95fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Prompt status"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.locked ? "This page shows the prompt summary, but the full prompt text and editor notes stay locked until your Academy access level matches the template." : "This template includes the main prompt, reuse guidance, and model-specific comparison notes in one place.")), promptModelsCovered.length ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-[#ffcfbf]/12 bg-[linear-gradient(180deg,rgba(255,207,191,0.08),rgba(255,255,255,0.03))] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-[#ffd8cd]" }, "Compared with"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, promptModelsCovered.length, " model", promptModelsCovered.length > 1 ? "s" : "", " documented for this prompt.")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold text-white" }, promptModelsCovered.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, promptModelsCovered.map((model) => /* @__PURE__ */ React.createElement("span", { key: model, className: "rounded-full border border-[#ffcfbf]/15 bg-[#ffcfbf]/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-[#fff0ea]" }, model)))) : null))))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 lg:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.045),rgba(148,163,184,0.03))] p-6 text-slate-200 shadow-[0_24px_70px_rgba(2,6,23,0.2)] md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4 border-b border-white/10 pb-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-[#ffd8cd]" }, "Prompt body"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "Prompt text and exclusions"))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-[#ffcfbf]/15 bg-[linear-gradient(180deg,rgba(255,207,191,0.08),rgba(255,255,255,0.03))] p-5 md:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-[#fff0ea]" }, promptHasFullAccess ? "Full prompt" : "Preview prompt"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs uppercase tracking-[0.16em] text-slate-500" }, promptHasFullAccess ? "Ready to paste into your generation workflow." : "Upgrade your Academy access to reveal the complete prompt text."))), /* @__PURE__ */ React.createElement("pre", { className: "mt-4 whitespace-pre-wrap rounded-[24px] border border-white/10 bg-slate-950/80 p-4 text-sm leading-7 text-slate-100 md:p-5" }, promptBody || "Prompt text is not available yet.")), item.negative_prompt ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5 md:p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Negative prompt"), /* @__PURE__ */ React.createElement("pre", { className: "mt-4 whitespace-pre-wrap rounded-[24px] border border-white/10 bg-slate-950/70 p-4 text-sm leading-7 text-slate-200 md:p-5" }, item.negative_prompt)) : null)), promptUsageNotes || promptWorkflowNotes ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.82))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)] md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Prompt guidance"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, "How to use this prompt")), !promptHasFullAccess ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Full notes visible with access") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-5 md:grid-cols-2" }, promptUsageNotes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-200/75" }, "Usage notes"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, promptUsageNotes)) : null, promptWorkflowNotes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-emerald-200/75" }, "Workflow notes"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 whitespace-pre-wrap text-sm leading-7 text-slate-200" }, promptWorkflowNotes)) : null)) : null, promptComparisons.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(255,183,139,0.12),transparent_30%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.92))] p-6 text-slate-200 shadow-[0_24px_80px_rgba(2,6,23,0.28)] md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-[#ffcfbf]" }, "AI model comparisons"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white md:text-3xl" }, "How different models respond to the same prompt"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300 md:text-base" }, "Use these notes to decide which provider fits the result you want before you start tuning or post-processing.")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-5 xl:grid-cols-2" }, promptComparisons.map((note, index2) => /* @__PURE__ */ React.createElement(PromptToolNoteCard, { key: `${note.provider || "provider"}-${note.model_name || "model"}-${index2}`, note, index: index2, galleryIndex: index2, onOpenImage: openPromptComparisonGallery })))) : null), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6 lg:sticky lg:top-6 lg:self-start" }, lessonTags.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Microtags"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, lessonTags.map((tag) => /* @__PURE__ */ React.createElement("span", { key: tag, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-sky-100" }, tag)))) : null, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(15,23,42,0.84))] p-6 text-slate-200 shadow-[0_18px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-400" }, "Best use case"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, promptComparisons[0]?.best_for || promptUsageNotes || lessonSummary))))) : /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.04] p-6 text-slate-200" }, pageType === "pack" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-8 text-slate-200" }, item.description), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, (item.prompts || []).map((prompt) => /* @__PURE__ */ React.createElement("div", { key: prompt.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, prompt.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, prompt.excerpt || prompt.prompt_preview))))) : null, pageType === "challenge" ? /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Brief"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.brief || item.description)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Rules"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 whitespace-pre-wrap text-sm leading-8 text-slate-200" }, item.rules || "No special rules posted yet."))), (item.submissions || []).length ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Approved submissions"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, item.submissions.map((submission) => /* @__PURE__ */ React.createElement("div", { key: submission.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, submission.artwork?.title || "Submission"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, submission.user?.name || "Unknown creator"))))) : null) : null)), /* @__PURE__ */ React.createElement(ImageLightbox, { gallery: lightboxGallery, onClose: () => setLightboxGallery(null), onNavigate: navigateLightboxGallery })); } -const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyShow }, Symbol.toStringTag, { value: "Module" })); @@ -13160,6 +13559,7 @@ const buildAdminNavGroups = (isAdmin) => [ label: "Academy", items: [ { label: "Academy Dashboard", href: "/moderation/academy/dashboard", icon: "fa-solid fa-graduation-cap" }, + { label: "Academy Courses", href: "/moderation/academy/courses", icon: "fa-solid fa-road" }, { label: "Academy Lessons", href: "/moderation/academy/lessons", icon: "fa-solid fa-book-open" }, { label: "Academy Prompts", href: "/moderation/academy/prompts", icon: "fa-solid fa-wand-magic-sparkles" }, { label: "Academy Challenges", href: "/moderation/academy/challenges", icon: "fa-solid fa-trophy" } @@ -13197,7 +13597,7 @@ function Sidebar({ pathname, isAdmin }) { ))); } function AdminLayout({ children, title, subtitle }) { - const { url, props } = X(); + const { url, props } = X$1(); const [mobileOpen, setMobileOpen] = reactExports.useState(false); const pathname = url.split("?")[0]; const currentUserIsAdmin = Boolean(props.auth?.user?.is_admin); @@ -13211,382 +13611,334 @@ function AdminLayout({ children, title, subtitle }) { /* @__PURE__ */ React.createElement("i", { className: mobileOpen ? "fa-solid fa-xmark" : "fa-solid fa-bars" }) )), mobileOpen && /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-30 lg:hidden" }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: () => setMobileOpen(false) }), /* @__PURE__ */ React.createElement("div", { className: "absolute left-0 top-0 h-full w-72 pt-14" }, /* @__PURE__ */ React.createElement(Sidebar, { pathname, isAdmin: currentUserIsAdmin }))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-1 flex-col lg:pl-8" }, /* @__PURE__ */ React.createElement("main", { className: "flex-1 px-6 py-8 pt-20 lg:pt-8" }, (title || subtitle) && /* @__PURE__ */ React.createElement("div", { className: "mb-8" }, title && /* @__PURE__ */ React.createElement("h1", { className: "text-2xl font-bold text-white" }, title), subtitle && /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, subtitle)), children))); } -const MONTH_NAMES = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December" -]; -const DAY_ABBR = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; -function pad(value) { - return String(value).padStart(2, "0"); +function laneKey(sectionId) { + return sectionId == null ? "unsectioned" : `section:${sectionId}`; } -function daysInMonth(year, month) { - return new Date(year, month + 1, 0).getDate(); +function sortSections(items = []) { + return [...items].sort((left, right) => { + const orderDiff = Number(left?.order_num || 0) - Number(right?.order_num || 0); + if (orderDiff !== 0) return orderDiff; + return Number(left?.id || 0) - Number(right?.id || 0); + }); } -function firstWeekday(year, month) { - const day = new Date(year, month, 1).getDay(); - return (day + 6) % 7; +function sortLessons(items = []) { + return [...items].sort((left, right) => { + const leftSection = left?.section_id == null ? -1 : Number(left.section_id); + const rightSection = right?.section_id == null ? -1 : Number(right.section_id); + if (leftSection !== rightSection) return leftSection - rightSection; + const orderDiff = Number(left?.order_num || 0) - Number(right?.order_num || 0); + if (orderDiff !== 0) return orderDiff; + return Number(left?.id || 0) - Number(right?.id || 0); + }); } -function toISODate(date) { - return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; +function buildLessonLanes(sections = [], lessons = []) { + const orderedSections = sortSections(sections); + const orderedLessons = sortLessons(lessons); + return [ + { + key: "unsectioned", + sectionId: null, + title: "Core lessons", + description: "Lessons shown before the course branches into sections.", + isVisible: true, + lessons: orderedLessons.filter((lesson) => lesson.section_id == null) + }, + ...orderedSections.map((section) => ({ + key: laneKey(section.id), + sectionId: section.id, + title: section.title, + description: section.description || "Section lessons appear together in this stage.", + isVisible: Boolean(section.is_visible), + lessons: orderedLessons.filter((lesson) => Number(lesson.section_id) === Number(section.id)) + })) + ]; } -function parseDatePart(value) { - if (!value) return null; - const [year, month, day] = value.split("-").map(Number); - if (!year || !month || !day) return null; - return new Date(year, month - 1, day); -} -function splitDateTime(value) { - if (!value) { - return { date: "", time: "" }; - } - const [date = "", time = ""] = String(value).split("T"); - return { - date, - time: time.slice(0, 5) - }; -} -function mergeDateTime(date, time) { - if (!date) return ""; - return `${date}T${time || "00:00"}`; -} -function maxDateValue(a, b2) { - if (!a) return b2 || ""; - if (!b2) return a || ""; - return a > b2 ? a : b2; -} -function minDateValue(a, b2) { - if (!a) return b2 || ""; - if (!b2) return a || ""; - return a < b2 ? a : b2; -} -function clampTimeToBounds(date, time, minDateTime, maxDateTime) { - const nextTime = time || "00:00"; - const minParts = splitDateTime(minDateTime); - const maxParts = splitDateTime(maxDateTime); - if (date && minParts.date === date && minParts.time && nextTime < minParts.time) { - return minParts.time; - } - if (date && maxParts.date === date && maxParts.time && nextTime > maxParts.time) { - return maxParts.time; - } - return nextTime; -} -function formatDisplay(value) { - if (!value) return ""; - const { date, time } = splitDateTime(value); - const parsed = parseDatePart(date); - if (!parsed) return ""; - return `${MONTH_NAMES[parsed.getMonth()].slice(0, 3)} ${parsed.getDate()}, ${parsed.getFullYear()}${time ? ` at ${time}` : ""}`; -} -function isSameDay(a, b2) { - return a?.getFullYear() === b2?.getFullYear() && a?.getMonth() === b2?.getMonth() && a?.getDate() === b2?.getDate(); -} -function CalendarGrid({ year, month, selectedDate, onSelect, minDate, maxDate }) { - const count = daysInMonth(year, month); - const start = firstWeekday(year, month); - const prevMonth = month - 1 < 0 ? 11 : month - 1; - const prevYear = month - 1 < 0 ? year - 1 : year; - const prevCount = daysInMonth(prevYear, prevMonth); - const cells = []; - for (let index2 = start - 1; index2 >= 0; index2 -= 1) { - cells.push({ - day: prevCount - index2, - current: false, - date: new Date(prevYear, prevMonth, prevCount - index2) - }); - } - for (let day = 1; day <= count; day += 1) { - cells.push({ day, current: true, date: new Date(year, month, day) }); - } - let nextDay = 1; - while (cells.length % 7 !== 0) { - cells.push({ day: nextDay, current: false, date: new Date(year, month + 1, nextDay) }); - nextDay += 1; - } - const today = /* @__PURE__ */ new Date(); - today.setHours(0, 0, 0, 0); - return /* @__PURE__ */ React.createElement("div", { className: "p-3" }, /* @__PURE__ */ React.createElement("div", { className: "mb-1 grid grid-cols-7" }, DAY_ABBR.map((day) => /* @__PURE__ */ React.createElement("div", { key: day, className: "py-1 text-center text-[10px] font-semibold text-slate-500" }, day))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-7 gap-y-0.5" }, cells.map((cell, index2) => { - const iso = toISODate(cell.date); - const selected = isSameDay(cell.date, selectedDate); - const todayCell = isSameDay(cell.date, today); - const disabled = minDate && iso < minDate || maxDate && iso > maxDate; - return /* @__PURE__ */ React.createElement( - "button", - { - key: `${iso}-${index2}`, - type: "button", - disabled, - onClick: () => onSelect(iso), - className: [ - "relative mx-auto flex h-8 w-8 items-center justify-center rounded-lg text-sm transition-all", - !cell.current ? "text-slate-600" : "", - cell.current && !selected && !disabled ? "text-white hover:bg-white/10" : "", - selected ? "bg-accent font-semibold text-white shadow shadow-accent/30" : "", - todayCell && !selected ? "text-accent ring-1 ring-accent/50" : "", - disabled ? "cursor-not-allowed opacity-30" : "cursor-pointer" - ].join(" ") - }, - cell.day - ); +function reindexLessonsFromLanes(sections = [], lessons = []) { + const lanes = buildLessonLanes(sections, lessons); + return lanes.flatMap((lane) => lane.lessons.map((lesson, index2) => ({ + ...lesson, + section_id: lane.sectionId, + order_num: index2 }))); } -function DateTimePicker({ - value = "", - onChange, - label, - placeholder, - error, - hint, - required = false, - clearable = false, - id, - disabled = false, - mode = "datetime", - minDate, - maxDate, - minDateTime, - maxDateTime, - className = "" +function moveLessonToPosition(sections = [], lessons = [], lessonId, nextSectionId, targetIndex) { + const lanes = buildLessonLanes(sections, lessons).map((lane) => ({ ...lane, lessons: [...lane.lessons] })); + let draggedLesson = null; + lanes.forEach((lane) => { + const lessonIndex = lane.lessons.findIndex((lesson) => Number(lesson.id) === Number(lessonId)); + if (lessonIndex === -1) return; + draggedLesson = { ...lane.lessons[lessonIndex], section_id: nextSectionId }; + lane.lessons.splice(lessonIndex, 1); + }); + if (!draggedLesson) return lessons; + const destinationLane = lanes.find((lane) => lane.sectionId === nextSectionId); + if (!destinationLane) return lessons; + const nextIndex = Math.max(0, Math.min(Number(targetIndex), destinationLane.lessons.length)); + destinationLane.lessons.splice(nextIndex, 0, draggedLesson); + return reindexLessonsFromLanes(sections, lanes.flatMap((lane) => lane.lessons)); +} +function shiftLesson(sections = [], lessons = [], lessonId, direction) { + const lanes = buildLessonLanes(sections, lessons); + for (const lane of lanes) { + const lessonIndex = lane.lessons.findIndex((lesson) => Number(lesson.id) === Number(lessonId)); + if (lessonIndex === -1) continue; + const nextIndex = lessonIndex + direction; + if (nextIndex < 0 || nextIndex >= lane.lessons.length) { + return lessons; + } + return moveLessonToPosition(sections, lessons, lessonId, lane.sectionId, nextIndex); + } + return lessons; +} +function placementSignature(lessons = []) { + return JSON.stringify(sortLessons(lessons).map((lesson) => ({ + id: Number(lesson.id), + section_id: lesson.section_id == null ? null : Number(lesson.section_id), + order_num: Number(lesson.order_num || 0) + }))); +} +function formatStepLabel(value) { + return `Step ${String(value).padStart(2, "0")}`; +} +function resolveDraggedLessonId(event, fallbackLessonId = null) { + const nativeLessonId = event?.dataTransfer?.getData("text/plain") || ""; + if (nativeLessonId !== "") { + return Number(nativeLessonId); + } + return fallbackLessonId == null ? null : Number(fallbackLessonId); +} +function FormCard({ title, description, children }) { + return /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/[0.08] bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Course builder"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold tracking-[-0.04em] text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-400" }, description) : null), children); +} +function CheckboxCardField$1({ label, checked, onChange, description }) { + return /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer items-start gap-4 rounded-[28px] border px-5 py-4 transition ${checked ? "border-[#f39a24]/35 bg-[#f39a24]/10" : "border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "sr-only" }), /* @__PURE__ */ React.createElement("span", { className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border text-sm transition ${checked ? "border-[#f39a24] bg-[#f39a24] text-white" : "border-white/10 bg-[#151a29] text-transparent"}` }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-check" })), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "block font-semibold text-white" }, label), description ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-xs leading-5 text-slate-400" }, description) : null)); +} +function EditableSectionCard({ section }) { + const form = G$1({ + title: section.title || "", + slug: section.slug || "", + description: section.description || "", + order_num: section.order_num || 0, + is_visible: Boolean(section.is_visible) + }); + return /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { + event.preventDefault(); + form.patch(section.updateUrl); + }, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Title"), /* @__PURE__ */ React.createElement("input", { value: form.data.title, onChange: (event) => form.setData("title", event.target.value), className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("input", { value: form.data.slug, onChange: (event) => form.setData("slug", event.target.value), className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200 lg:col-span-2" }, /* @__PURE__ */ React.createElement("span", null, "Description"), /* @__PURE__ */ React.createElement("textarea", { value: form.data.description, onChange: (event) => form.setData("description", event.target.value), rows: 3, className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Order"), /* @__PURE__ */ React.createElement("input", { type: "number", value: form.data.order_num, onChange: (event) => form.setData("order_num", event.target.value), className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement( + CheckboxCardField$1, + { + label: "Visible section", + checked: Boolean(form.data.is_visible), + onChange: (event) => form.setData("is_visible", event.target.checked), + description: "Hide the whole section from the public course outline without deleting its lessons or structure." + } + )), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save section"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + if (!window.confirm("Delete this section?")) return; + At.delete(section.destroyUrl); + }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Delete"))); +} +function EditableCourseLessonCard({ courseLesson, sectionOptions, stepLabel }) { + const form = G$1({ + section_id: courseLesson.section_id || "", + order_num: courseLesson.order_num || 0, + is_required: Boolean(courseLesson.is_required), + access_override: courseLesson.access_override || "", + unlock_after_lesson_id: courseLesson.unlock_after_lesson_id || "" + }); + const accessOptions = [ + { value: "", label: "Use lesson access" }, + { value: "free", label: "Free" }, + { value: "creator", label: "Creator" }, + { value: "pro", label: "Pro" }, + { value: "premium", label: "Premium" } + ]; + reactExports.useEffect(() => { + form.setData("section_id", courseLesson.section_id || ""); + form.setData("order_num", courseLesson.order_num || 0); + }, [courseLesson.order_num, courseLesson.section_id]); + return /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { + event.preventDefault(); + form.patch(courseLesson.updateUrl); + }, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, stepLabel ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, stepLabel) : null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, courseLesson.category || "Academy", " · ", courseLesson.difficulty || "lesson")), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-lg font-semibold text-white" }, courseLesson.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, courseLesson.excerpt || "This lesson is attached to the course."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Section", value: form.data.section_id, onChange: (nextValue) => form.setData("section_id", nextValue || ""), options: sectionOptions, searchable: false, className: "rounded-2xl bg-white/[0.04]" }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Order"), /* @__PURE__ */ React.createElement("input", { type: "number", value: form.data.order_num, onChange: (event) => form.setData("order_num", event.target.value), className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(NovaSelect, { label: "Access override", value: form.data.access_override, onChange: (nextValue) => form.setData("access_override", nextValue || ""), options: accessOptions, searchable: false, className: "rounded-2xl bg-white/[0.04]" })), /* @__PURE__ */ React.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React.createElement( + CheckboxCardField$1, + { + label: "Required for course completion", + checked: Boolean(form.data.is_required), + onChange: (event) => form.setData("is_required", event.target.checked), + description: "Only required lessons count toward the course completion percentage." + } + )), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save lesson settings"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + if (!window.confirm("Remove this lesson from the course?")) return; + At.delete(courseLesson.destroyUrl); + }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Detach"))); +} +function ReorderLessonCard({ + lesson, + lane, + laneIndex, + laneCount, + globalStepNumber, + isDragging, + isDropTarget, + onDragStart, + onDragEnd, + onDragOver, + onDrop, + onMoveUp, + onMoveDown, + onMoveLeft, + onMoveRight }) { - const today = /* @__PURE__ */ new Date(); - const initial = splitDateTime(value); - const initialDate = parseDatePart(initial.date) || today; - const [open, setOpen] = reactExports.useState(false); - const [dropPos, setDropPos] = reactExports.useState({ top: 0, left: 0, width: 320 }); - const [viewYear, setViewYear] = reactExports.useState(initialDate.getFullYear()); - const [viewMonth, setViewMonth] = reactExports.useState(initialDate.getMonth()); - const [draftDate, setDraftDate] = reactExports.useState(initial.date); - const nowTime = `${pad(today.getHours())}:${pad(today.getMinutes())}`; - const defaultDraftTime = (function() { - const baseDate = initial.date || toISODate(initialDate); - const candidate = initial.time || nowTime; - return clampTimeToBounds(baseDate, candidate, minDateTime, maxDateTime); - })(); - const [draftTime, setDraftTime] = reactExports.useState(defaultDraftTime); - const effectivePlaceholder = placeholder || (mode === "date" ? "Pick a date" : "Pick a date and time"); - const triggerRef = reactExports.useRef(null); - const inputId = id ?? (label ? `dtp-${label.toLowerCase().replace(/\s+/g, "-")}` : "date-time-picker"); - const panelId = `dtp-panel-${inputId}`; - reactExports.useEffect(() => { - const next = splitDateTime(value); - setDraftDate(next.date); - const fallbackTime = (() => { - const candidate = next.time || `${pad((/* @__PURE__ */ new Date()).getHours())}:${pad((/* @__PURE__ */ new Date()).getMinutes())}`; - const dateForClamp = next.date || toISODate(initialDate); - return clampTimeToBounds(dateForClamp, candidate, minDateTime, maxDateTime); - })(); - setDraftTime(next.time || fallbackTime); - const nextDate = parseDatePart(next.date); - if (nextDate) { - setViewYear(nextDate.getFullYear()); - setViewMonth(nextDate.getMonth()); - } - }, [value]); - const measure = reactExports.useCallback(() => { - if (!triggerRef.current) return; - const rect = triggerRef.current.getBoundingClientRect(); - const panelWidth = Math.max(rect.width, 320); - const panelHeight = 420; - const openUp = window.innerHeight - rect.bottom < panelHeight + 8 && rect.top > panelHeight + 8; - setDropPos({ - top: openUp ? rect.top - panelHeight - 4 : rect.bottom + 4, - left: Math.min(rect.left, window.innerWidth - panelWidth - 8), - width: panelWidth - }); - }, []); - const openPicker = reactExports.useCallback(() => { - if (disabled) return; - measure(); - setOpen(true); - }, [disabled, measure]); - reactExports.useEffect(() => { - if (!open) return void 0; - const handleMouseDown = (event) => { - if (!triggerRef.current?.contains(event.target) && !document.getElementById(panelId)?.contains(event.target)) { - setOpen(false); - } - }; - document.addEventListener("mousedown", handleMouseDown); - return () => document.removeEventListener("mousedown", handleMouseDown); - }, [open, panelId]); - reactExports.useEffect(() => { - if (!open) return void 0; - const handleScroll = (event) => { - if (document.getElementById(panelId)?.contains(event.target)) return; - setOpen(false); - }; - const handleResize = () => setOpen(false); - window.addEventListener("scroll", handleScroll, true); - window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("scroll", handleScroll, true); - window.removeEventListener("resize", handleResize); - }; - }, [open, panelId]); - const applyValue = reactExports.useCallback((date, time) => { - if (!date) { - onChange?.(""); - return; - } - onChange?.(mode === "date" ? date : mergeDateTime(date, time)); - }, [mode, onChange]); - const handleDateSelect = (nextDate) => { - const nextTime = clampTimeToBounds(nextDate, draftTime, minDateTime, maxDateTime); - setDraftDate(nextDate); - setDraftTime(nextTime); - applyValue(nextDate, nextTime); - }; - const handleTimeChange = (event) => { - const nextTime = clampTimeToBounds(draftDate, event.target.value, minDateTime, maxDateTime); - setDraftTime(nextTime); - applyValue(draftDate, nextTime); - }; - const clearValue = (event) => { - event.stopPropagation(); - const now = `${pad((/* @__PURE__ */ new Date()).getHours())}:${pad((/* @__PURE__ */ new Date()).getMinutes())}`; - setDraftDate(""); - setDraftTime(now); - onChange?.(""); - }; - const prevMonth = () => { - if (viewMonth === 0) { - setViewMonth(11); - setViewYear((current) => current - 1); - return; - } - setViewMonth((current) => current - 1); - }; - const nextMonth = () => { - if (viewMonth === 11) { - setViewMonth(0); - setViewYear((current) => current + 1); - return; - } - setViewMonth((current) => current + 1); - }; - const triggerClass = [ - "relative flex h-[42px] w-full cursor-pointer items-center gap-2 rounded-xl border px-3.5 text-sm transition-all duration-150", - "bg-white/[0.06] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0", - error ? "border-red-500/60 focus-visible:ring-red-500/40" : open ? "border-accent/50 ring-2 ring-accent/40" : "border-white/12 hover:border-white/22", - disabled ? "pointer-events-none cursor-not-allowed opacity-50" : "", - className - ].join(" "); - const selectedDate = parseDatePart(draftDate); - const minDateTimeParts = splitDateTime(minDateTime); - const maxDateTimeParts = splitDateTime(maxDateTime); - const effectiveMinDate = maxDateValue(minDate, minDateTimeParts.date); - const effectiveMaxDate = minDateValue(maxDate, maxDateTimeParts.date); - const minTime = draftDate && draftDate === minDateTimeParts.date ? minDateTimeParts.time || void 0 : void 0; - const maxTime = draftDate && draftDate === maxDateTimeParts.date ? maxDateTimeParts.time || void 0 : void 0; - return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1.5" }, label && /* @__PURE__ */ React.createElement("label", { htmlFor: inputId, className: "text-sm font-medium text-white/85 select-none" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "ml-1 text-red-400" }, "*")), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement( "div", { - ref: triggerRef, - id: inputId, - role: "button", - tabIndex: disabled ? -1 : 0, - "aria-label": label ?? effectivePlaceholder, - className: triggerClass, - onClick: openPicker, - onKeyDown: (event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - openPicker(); - } - } + draggable: true, + onDragStart: (event) => { + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("text/plain", String(lesson.id)); + onDragStart(lesson.id); + }, + onDragEnd, + onDragOver: (event) => { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + onDragOver(lesson.id); + }, + onDrop: (event) => { + event.preventDefault(); + event.stopPropagation(); + onDrop(event, lane.sectionId, lesson.id); + }, + className: [ + "rounded-[24px] border bg-black/20 p-4 transition", + isDragging ? "border-sky-300/40 opacity-60" : "border-white/10 hover:border-white/20", + isDropTarget ? "ring-2 ring-sky-300/35" : "" + ].join(" ") }, - /* @__PURE__ */ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", className: "shrink-0 text-slate-500", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { x: "1", y: "2.5", width: "12", height: "10.5", rx: "1.5", stroke: "currentColor", strokeWidth: "1.3" }), /* @__PURE__ */ React.createElement("path", { d: "M1 6h12", stroke: "currentColor", strokeWidth: "1.3" }), /* @__PURE__ */ React.createElement("path", { d: "M4 1v3M10 1v3", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "9", r: "0.75", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "7", cy: "9", r: "0.75", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "9.5", cy: "9", r: "0.75", fill: "currentColor" })), - /* @__PURE__ */ React.createElement("span", { className: `flex-1 truncate ${value ? "text-white" : "text-slate-500"}` }, value ? formatDisplay(value) : effectivePlaceholder), - clearable && value && /* @__PURE__ */ React.createElement( - "button", - { - type: "button", - tabIndex: -1, - onClick: clearValue, - className: "flex h-5 w-5 items-center justify-center rounded text-slate-500 transition-colors hover:text-white", - "aria-label": "Clear date and time" - }, - /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M1 1l8 8M9 1L1 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })) - ) - ), error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "text-xs text-red-400" }, error), !error && hint && /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500" }, hint), open && reactDomExports.createPortal( - /* @__PURE__ */ React.createElement( - "div", - { - id: panelId, - className: "fixed z-[500] overflow-hidden rounded-2xl border border-white/12 bg-nova-900 shadow-2xl shadow-black/50", - style: { top: dropPos.top, left: dropPos.left, width: dropPos.width } - }, - /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between px-3 pt-3" }, /* @__PURE__ */ React.createElement( - "button", - { - type: "button", - onClick: prevMonth, - className: "flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white", - "aria-label": "Previous month" - }, - /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M7 1L3 5l4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })) - ), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, MONTH_NAMES[viewMonth], " ", viewYear), /* @__PURE__ */ React.createElement( - "button", - { - type: "button", - onClick: nextMonth, - className: "flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white", - "aria-label": "Next month" - }, - /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M3 1l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })) - )), - /* @__PURE__ */ React.createElement( - CalendarGrid, - { - year: viewYear, - month: viewMonth, - selectedDate, - onSelect: handleDateSelect, - minDate: effectiveMinDate, - maxDate: effectiveMaxDate - } - ), - /* @__PURE__ */ React.createElement("div", { className: "border-t border-white/8 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: `grid gap-3 ${mode === "date" ? "" : "sm:grid-cols-[minmax(0,1fr)_7rem] sm:items-end"}` }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Selected date"), /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-sm text-white" }, draftDate ? formatDisplay(draftDate) : "Pick a day")), mode !== "date" ? /* @__PURE__ */ React.createElement("label", { className: "grid gap-1.5 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Time"), /* @__PURE__ */ React.createElement( - "input", - { - type: "time", - value: draftTime, - onChange: handleTimeChange, - min: minTime, - max: maxTime, - className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-white outline-none transition focus:border-accent/50 focus:ring-2 focus:ring-accent/40" - } - )) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex items-center justify-between" }, /* @__PURE__ */ React.createElement( - "button", - { - type: "button", - onClick: () => handleDateSelect(toISODate(/* @__PURE__ */ new Date())), - className: "text-xs font-medium text-accent transition-colors hover:text-accent/80" - }, - "Today" - ), /* @__PURE__ */ React.createElement( - "button", - { - type: "button", - onClick: () => setOpen(false), - className: "rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-medium text-white transition hover:bg-white/[0.08]" - }, - "Done" - ))) - ), - document.body - )); + /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, formatStepLabel(globalStepNumber)), lesson.is_required ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100" }, "Required") : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Optional")), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-base font-semibold tracking-[-0.03em] text-white" }, lesson.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-[11px] uppercase tracking-[0.16em] text-slate-500" }, lesson.category || "Academy", " · ", lesson.difficulty || "lesson", " · order ", Number(lesson.order_num) + 1), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, lesson.excerpt || "Drag this lesson to reposition it inside the course flow.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onMoveUp, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40", disabled: lane.lessons[0]?.id === lesson.id }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onMoveDown, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40", disabled: lane.lessons[lane.lessons.length - 1]?.id === lesson.id }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-down" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onMoveLeft, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40", disabled: laneIndex <= 0 }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onMoveRight, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40", disabled: laneIndex >= laneCount - 1 }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" })))) + ); } +function AcademyCourseBuilder({ course, sections = [], courseLessons = [], availableLessons = [], routes = {} }) { + const flash = X$1().props.flash || {}; + const sectionForm = G$1({ title: "", slug: "", description: "", order_num: sections.length, is_visible: true }); + const attachForm = G$1({ lesson_id: "", section_id: "", order_num: courseLessons.length, is_required: true, access_override: "", unlock_after_lesson_id: "" }); + const [draftLessons, setDraftLessons] = reactExports.useState(() => reindexLessonsFromLanes(sections, courseLessons)); + const [draggedLessonId, setDraggedLessonId] = reactExports.useState(null); + const [dropTargetLessonId, setDropTargetLessonId] = reactExports.useState(null); + const [reorderProcessing, setReorderProcessing] = reactExports.useState(false); + const sectionOptions = [{ value: "", label: "Unsectioned" }, ...sections.map((section) => ({ value: section.id, label: section.title }))]; + const lessonOptions = availableLessons.map((lesson) => ({ value: lesson.id, label: `${lesson.title}${lesson.attached ? " · attached" : ""}` })); + const attachableLessonOptions = availableLessons.filter((lesson) => !lesson.attached).map((lesson) => ({ value: lesson.id, label: lesson.title })); + const accessOverrideOptions = [ + { value: "", label: "Use lesson access" }, + { value: "free", label: "Free" }, + { value: "creator", label: "Creator" }, + { value: "pro", label: "Pro" }, + { value: "premium", label: "Premium" } + ]; + const lessonLanes = reactExports.useMemo(() => buildLessonLanes(sections, draftLessons), [sections, draftLessons]); + const reorderDirty = reactExports.useMemo(() => placementSignature(draftLessons) !== placementSignature(reindexLessonsFromLanes(sections, courseLessons)), [courseLessons, draftLessons, sections]); + const courseLessonMap = reactExports.useMemo(() => new Map(courseLessons.map((courseLesson) => [Number(courseLesson.id), courseLesson])), [courseLessons]); + const orderedCourseLessons = reactExports.useMemo(() => reindexLessonsFromLanes(sections, courseLessons.map((courseLesson) => ({ + ...courseLesson, + ...draftLessons.find((draftLesson) => Number(draftLesson.id) === Number(courseLesson.id)) || {} + }))), [courseLessons, draftLessons, sections]); + const globalStepMap = reactExports.useMemo(() => new Map(lessonLanes.flatMap((lane) => lane.lessons).map((lesson, index2) => [Number(lesson.id), index2 + 1])), [lessonLanes]); + reactExports.useEffect(() => { + setDraftLessons(reindexLessonsFromLanes(sections, courseLessons)); + }, [courseLessons, sections]); + const moveLessonAcrossLanes = (lessonId, laneIndexDelta) => { + const lanes = buildLessonLanes(sections, draftLessons); + const currentLaneIndex = lanes.findIndex((lane) => lane.lessons.some((lesson) => Number(lesson.id) === Number(lessonId))); + if (currentLaneIndex === -1) return; + const nextLane = lanes[currentLaneIndex + laneIndexDelta]; + if (!nextLane) return; + setDraftLessons((current) => moveLessonToPosition(sections, current, lessonId, nextLane.sectionId, nextLane.lessons.length)); + }; + const saveReorder = () => { + setReorderProcessing(true); + At.patch(routes.reorder, { + sections: sortSections(sections).map((section, index2) => ({ id: section.id, order_num: index2 })), + lessons: reindexLessonsFromLanes(sections, draftLessons).map((lesson) => ({ + id: lesson.id, + order_num: lesson.order_num, + section_id: lesson.section_id + })) + }, { + preserveScroll: true, + onFinish: () => setReorderProcessing(false) + }); + }; + return /* @__PURE__ */ React.createElement(AdminLayout, { title: `${course.title} Builder`, subtitle: "Arrange sections, attach lessons, and control course flow." }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${course.title} Builder` }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: routes.index, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back to courses"), /* @__PURE__ */ React.createElement(xe, { href: routes.edit, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Edit course"), /* @__PURE__ */ React.createElement(xe, { href: routes.preview, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Preview public page")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_420px]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement(FormCard, { title: "Structure overview", description: "The course builder keeps the lesson content reusable while controlling order, access, and required progress here." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Sections"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold text-white" }, sections.length)), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Attached lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold text-white" }, courseLessons.length)), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, "Published course lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold text-white" }, courseLessons.filter((lesson) => lesson.title).length)))), /* @__PURE__ */ React.createElement(FormCard, { title: "Sections", description: "Chapters are optional, but they help group lessons into cleaner stages." }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, sections.map((section) => /* @__PURE__ */ React.createElement(EditableSectionCard, { key: section.id, section })))), /* @__PURE__ */ React.createElement(FormCard, { title: "Lesson flow", description: "Manage the course order as one visual path. Drag lessons between lanes, use arrows for exact moves, then save the sequence in one action." }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Flow board"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-300" }, "Core lessons stay first, then each visible section keeps its own ordered lane.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, reorderDirty ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/25 bg-amber-300/10 px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Unsaved order changes") : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-emerald-100" }, "Order is saved"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setDraftLessons(reindexLessonsFromLanes(sections, courseLessons)), disabled: !reorderDirty || reorderProcessing, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white disabled:opacity-40" }, "Reset"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveReorder, disabled: !reorderDirty || reorderProcessing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100 disabled:opacity-40" }, reorderProcessing ? "Saving order..." : "Save order"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 xl:grid-cols-2" }, lessonLanes.map((lane, laneIndex) => /* @__PURE__ */ React.createElement( + "section", + { + key: lane.key, + onDragOver: (event) => event.preventDefault(), + onDrop: (event) => { + event.preventDefault(); + event.stopPropagation(); + const activeLessonId = resolveDraggedLessonId(event, draggedLessonId); + if (activeLessonId == null) return; + setDraftLessons((current) => moveLessonToPosition(sections, current, activeLessonId, lane.sectionId, lane.lessons.length)); + setDraggedLessonId(null); + setDropTargetLessonId(null); + }, + className: `rounded-[26px] border p-4 ${lane.isVisible ? "border-white/10 bg-white/[0.03]" : "border-amber-300/20 bg-amber-300/8"}` + }, + /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, lane.sectionId == null ? "Core lane" : "Section lane"), !lane.isVisible ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Hidden on public page") : null), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-lg font-semibold tracking-[-0.03em] text-white" }, lane.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, lane.description)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, lane.lessons.length, " lessons")), + /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3 min-h-20" }, lane.lessons.length ? lane.lessons.map((lesson) => /* @__PURE__ */ React.createElement( + ReorderLessonCard, + { + key: lesson.id, + lesson, + lane, + laneIndex, + laneCount: lessonLanes.length, + globalStepNumber: globalStepMap.get(Number(lesson.id)) || 1, + isDragging: Number(draggedLessonId) === Number(lesson.id), + isDropTarget: Number(dropTargetLessonId) === Number(lesson.id), + onDragStart: setDraggedLessonId, + onDragEnd: () => { + setDraggedLessonId(null); + setDropTargetLessonId(null); + }, + onDragOver: setDropTargetLessonId, + onDrop: (event, targetSectionId, targetLessonId) => { + const activeLessonId = resolveDraggedLessonId(event, draggedLessonId); + if (activeLessonId == null) return; + if (Number(activeLessonId) === Number(targetLessonId)) return; + const targetLane = lessonLanes.find((entry) => entry.sectionId === targetSectionId); + const targetIndex = Math.max(0, (targetLane?.lessons || []).findIndex((entry) => Number(entry.id) === Number(targetLessonId))); + setDraftLessons((current) => moveLessonToPosition(sections, current, activeLessonId, targetSectionId, targetIndex)); + setDraggedLessonId(null); + setDropTargetLessonId(null); + }, + onMoveUp: () => setDraftLessons((current) => shiftLesson(sections, current, lesson.id, -1)), + onMoveDown: () => setDraftLessons((current) => shiftLesson(sections, current, lesson.id, 1)), + onMoveLeft: () => moveLessonAcrossLanes(lesson.id, -1), + onMoveRight: () => moveLessonAcrossLanes(lesson.id, 1) + } + )) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-dashed border-white/10 bg-black/20 px-4 py-6 text-sm text-slate-500" }, "Drop lessons here to move them into this lane.")) + )))), /* @__PURE__ */ React.createElement(FormCard, { title: "Attached lessons", description: "Each lesson stays reusable across courses. Adjust order, requirement status, and access overrides here." }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, orderedCourseLessons.map((courseLesson, index2) => /* @__PURE__ */ React.createElement(EditableCourseLessonCard, { key: courseLesson.id, courseLesson: { ...courseLessonMap.get(Number(courseLesson.id)), ...courseLesson }, sectionOptions, stepLabel: formatStepLabel(index2 + 1) }))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6 xl:sticky xl:top-6 xl:self-start" }, /* @__PURE__ */ React.createElement(FormCard, { title: "Create section", description: "Create a chapter before attaching lessons into it." }, /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { + event.preventDefault(); + sectionForm.post(routes.sectionStore); + }, className: "space-y-4" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Title"), /* @__PURE__ */ React.createElement("input", { value: sectionForm.data.title, onChange: (event) => sectionForm.setData("title", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("input", { value: sectionForm.data.slug, onChange: (event) => sectionForm.setData("slug", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none", placeholder: "auto-generated if blank" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Description"), /* @__PURE__ */ React.createElement("textarea", { value: sectionForm.data.description, onChange: (event) => sectionForm.setData("description", event.target.value), rows: 4, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm leading-7 text-white outline-none" })), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: sectionForm.processing, className: "w-full rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, sectionForm.processing ? "Creating..." : "Create section"))), /* @__PURE__ */ React.createElement(FormCard, { title: "Attach lesson", description: "Attach an existing Academy lesson to this course without duplicating content." }, /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => { + event.preventDefault(); + attachForm.post(routes.attachLesson); + }, className: "space-y-4" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Lesson", value: attachForm.data.lesson_id, onChange: (nextValue) => attachForm.setData("lesson_id", nextValue || ""), options: attachableLessonOptions.length ? attachableLessonOptions : lessonOptions, className: "rounded-2xl bg-black/20" }), /* @__PURE__ */ React.createElement(NovaSelect, { label: "Section", value: attachForm.data.section_id, onChange: (nextValue) => attachForm.setData("section_id", nextValue || ""), options: sectionOptions, searchable: false, className: "rounded-2xl bg-black/20" }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Order"), /* @__PURE__ */ React.createElement("input", { type: "number", value: attachForm.data.order_num, onChange: (event) => attachForm.setData("order_num", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none" })), /* @__PURE__ */ React.createElement(NovaSelect, { label: "Access override", value: attachForm.data.access_override, onChange: (nextValue) => attachForm.setData("access_override", nextValue || ""), options: accessOverrideOptions, searchable: false, className: "rounded-2xl bg-black/20" }), /* @__PURE__ */ React.createElement( + CheckboxCardField$1, + { + label: "Required for completion", + checked: Boolean(attachForm.data.is_required), + onChange: (event) => attachForm.setData("is_required", event.target.checked), + description: "Only required lessons count toward course completion." + } + ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: attachForm.processing || !attachForm.data.lesson_id, className: "w-full rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100" }, attachForm.processing ? "Attaching..." : "Attach lesson")))))); +} +const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: AcademyCourseBuilder +}, Symbol.toStringTag, { value: "Module" })); const MentionList = reactExports.forwardRef(function MentionList2({ items, command }, ref2) { const [selectedIndex, setSelectedIndex] = reactExports.useState(0); reactExports.useEffect(() => setSelectedIndex(0), [items]); @@ -13886,7 +14238,7 @@ function EmojiPicker({ onSelect, editor }) { { ref: buttonRef, type: "button", - onClick: () => setOpen((v) => !v), + onClick: () => setOpen((v2) => !v2), title: "Insert emoji", "aria-label": "Open emoji picker", "aria-expanded": open, @@ -14508,6 +14860,17 @@ function ToolbarBtn$2({ onClick, active, disabled, title, children, className = function Divider$1() { return /* @__PURE__ */ React.createElement("div", { className: "mx-1 h-5 w-px bg-white/10" }); } +function getRootFontSizePx() { + if (typeof window === "undefined") { + return 16; + } + return Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize) || 16; +} +function formatViewportHeightLabel(value) { + const rounded = Number(value || 0); + const displayValue = Number.isInteger(rounded) ? rounded : Number(rounded.toFixed(1)); + return `${displayValue}rem`; +} function normalizeHttpUrl(rawValue) { const trimmed = String(rawValue || "").trim(); if (trimmed === "") { @@ -14816,7 +15179,7 @@ function ArtworkPickerDialog({ className: "min-w-0 flex-1 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" } ), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onSearch, className: "rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, "Search"))), /* @__PURE__ */ React.createElement("div", { className: "nova-scrollbar max-h-[60vh] overflow-y-auto px-6 py-5" }, loading ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-sm text-slate-300" }, "Searching artworks…") : null, !loading && (!Array.isArray(items) || items.length === 0) ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-400" }, "No artworks found yet. Try a broader title or creator search.") : null, !loading && Array.isArray(items) && items.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, items.map((item) => { - const previewImage = item.image || item.avatar || ""; + const previewImage2 = item.image || item.avatar || ""; return /* @__PURE__ */ React.createElement( "button", { @@ -14825,7 +15188,7 @@ function ArtworkPickerDialog({ onClick: () => onSelect?.(item), className: "flex items-center gap-4 rounded-[24px] border border-white/10 bg-black/20 p-3 text-left transition hover:border-white/20 hover:bg-white/[0.04]" }, - /* @__PURE__ */ React.createElement("div", { className: "h-20 w-28 shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03]" }, previewImage ? /* @__PURE__ */ React.createElement("img", { src: previewImage, alt: item.title, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full items-center justify-center text-xs uppercase tracking-[0.18em] text-slate-500" }, "No thumb")), + /* @__PURE__ */ React.createElement("div", { className: "h-20 w-28 shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03]" }, previewImage2 ? /* @__PURE__ */ React.createElement("img", { src: previewImage2, alt: item.title, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full items-center justify-center text-xs uppercase tracking-[0.18em] text-slate-500" }, "No thumb")), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), item.subtitle ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.14em] text-slate-500" }, item.subtitle) : null, item.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 line-clamp-2 text-xs leading-5 text-slate-400" }, item.description) : null) ); })) : null), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onClose, className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Cancel"))) @@ -15145,10 +15508,16 @@ function AssetPickerDialog({ function Toolbar$1({ editor, advancedNews = false, - sourceMode = false, + activeSourceMode = null, + sourceModeLabel = "HTML", + sourceModeTitle = "View or edit source HTML", + secondarySourceModeLabel = null, + secondarySourceModeTitle = "", showStructureOutlines = false, showComparisonTool = false, + fullHeightMode = false, onToggleSourceMode, + onToggleSecondarySourceMode, onToggleStructureOutlines, onInsertArtwork, onInsertImage, @@ -15160,7 +15529,7 @@ function Toolbar$1({ editorViewportHeight, onIncreaseEditorViewportHeight, onDecreaseEditorViewportHeight, - onResetEditorViewportHeight + onToggleFullHeightMode }) { if (!editor) return null; const addLink = reactExports.useCallback(() => { @@ -15173,7 +15542,7 @@ function Toolbar$1({ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); } }, [editor]); - return /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-0.5 border-b border-white/[0.06] px-2.5 py-2" }, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBold().run(), active: editor.isActive("bold"), title: "Bold (Ctrl+B)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M6 4h8a4 4 0 014 4 4 4 0 01-4 4H6zm0 8h9a4 4 0 014 4 4 4 0 01-4 4H6z" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive("italic"), title: "Italic (Ctrl+I)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "19", y1: "4", x2: "10", y2: "4" }), /* @__PURE__ */ React.createElement("line", { x1: "14", y1: "20", x2: "5", y2: "20" }), /* @__PURE__ */ React.createElement("line", { x1: "15", y1: "4", x2: "9", y2: "20" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleUnderline().run(), active: editor.isActive("underline"), title: "Underline (Ctrl+U)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("path", { d: "M6 3v7a6 6 0 006 6 6 6 0 006-6V3" }), /* @__PURE__ */ React.createElement("line", { x1: "4", y1: "21", x2: "20", y2: "21" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleStrike().run(), active: editor.isActive("strike"), title: "Strikethrough" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "4", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ React.createElement("path", { d: "M17.5 7.5c0-2-1.5-3.5-5.5-3.5S6.5 5.5 6.5 7.5c0 4 11 4 11 8 0 2-1.5 3.5-5.5 3.5s-5.5-1.5-5.5-3.5" }))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), active: editor.isActive("heading", { level: 2 }), title: "Heading 2" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "H2")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), active: editor.isActive("heading", { level: 3 }), title: "Heading 3" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "H3")), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBulletList().run(), active: editor.isActive("bulletList"), title: "Bullet list" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "6", x2: "20", y2: "6" }), /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "18", x2: "20", y2: "18" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "6", r: "1", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "12", r: "1", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "18", r: "1", fill: "currentColor" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleOrderedList().run(), active: editor.isActive("orderedList"), title: "Numbered list" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "6", x2: "21", y2: "6" }), /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "12", x2: "21", y2: "12" }), /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "18", x2: "21", y2: "18" }), /* @__PURE__ */ React.createElement("text", { x: "3", y: "8", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "1"), /* @__PURE__ */ React.createElement("text", { x: "3", y: "14", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "2"), /* @__PURE__ */ React.createElement("text", { x: "3", y: "20", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "3"))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBlockquote().run(), active: editor.isActive("blockquote"), title: "Quote" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311C9.591 11.68 11 13.24 11 15.14c0 .94-.36 1.84-1.001 2.503A3.34 3.34 0 017.559 18.6a3.77 3.77 0 01-2.976-.879zm10.4 0C13.953 16.227 13.4 15 13.4 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.986.169 3.395 1.729 3.395 3.629 0 .94-.36 1.84-1.001 2.503a3.34 3.34 0 01-2.44.957 3.77 3.77 0 01-2.976-.879z" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleCodeBlock().run(), active: editor.isActive("codeBlock"), title: "Code block" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "16 18 22 12 16 6" }), /* @__PURE__ */ React.createElement("polyline", { points: "8 6 2 12 8 18" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleCode().run(), active: editor.isActive("code"), title: "Inline code" }, /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[11px] font-bold" }, "{}")), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: addLink, active: editor.isActive("link"), title: "Link" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" }), /* @__PURE__ */ React.createElement("path", { d: "M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertImage, title: "Insert image" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), /* @__PURE__ */ React.createElement("polyline", { points: "21 15 16 10 5 21" }))), showComparisonTool ? /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertComparison, title: "Insert image comparison" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "4", width: "8", height: "16", rx: "1.5" }), /* @__PURE__ */ React.createElement("rect", { x: "13", y: "4", width: "8", height: "16", rx: "1.5" }), /* @__PURE__ */ React.createElement("path", { d: "M9 8h1M14 8h1M9 16h1M14 16h1" }))) : null, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertTable, title: "Insert table" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M3 9h18M3 15h18M9 3v18M15 3v18" }))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().setHorizontalRule().run(), title: "Horizontal rule" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "3", y1: "12", x2: "21", y2: "12" }))), /* @__PURE__ */ React.createElement(EmojiPicker, { editor }), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().insertContent("@").run(), title: "Mention a user (type @username)" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "@")), advancedNews ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleSourceMode, active: sourceMode, title: "View or edit source HTML", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "HTML")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleStructureOutlines, active: showStructureOutlines, title: "Outline blocks (p, div, figure, list)", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "DOM")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertArtwork, title: "Embed artwork", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Art")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertSocialEmbed, title: "Embed social post", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Social")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertVideoEmbed, title: "Embed YouTube", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "YT")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertHashtag, title: "Insert hashtag", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "#"))) : null, /* @__PURE__ */ React.createElement("div", { className: "ml-auto flex items-center gap-0.5" }, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onDecreaseEditorViewportHeight, title: "Shorter editor", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "A-")), /* @__PURE__ */ React.createElement("div", { className: "mx-1 flex min-w-[5.25rem] items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, editorViewportHeight, "rem"), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onIncreaseEditorViewportHeight, title: "Taller editor", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "A+")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onResetEditorViewportHeight, title: "Reset editor height", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Fit")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().undo().run(), disabled: !editor.can().undo(), title: "Undo (Ctrl+Z)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "1 4 1 10 7 10" }), /* @__PURE__ */ React.createElement("path", { d: "M3.51 15a9 9 0 102.13-9.36L1 10" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: "Redo (Ctrl+Shift+Z)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "23 4 23 10 17 10" }), /* @__PURE__ */ React.createElement("path", { d: "M20.49 15a9 9 0 11-2.13-9.36L23 10" }))))); + return /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-0.5 border-b border-white/[0.06] px-2.5 py-2" }, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBold().run(), active: editor.isActive("bold"), title: "Bold (Ctrl+B)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M6 4h8a4 4 0 014 4 4 4 0 01-4 4H6zm0 8h9a4 4 0 014 4 4 4 0 01-4 4H6z" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive("italic"), title: "Italic (Ctrl+I)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "19", y1: "4", x2: "10", y2: "4" }), /* @__PURE__ */ React.createElement("line", { x1: "14", y1: "20", x2: "5", y2: "20" }), /* @__PURE__ */ React.createElement("line", { x1: "15", y1: "4", x2: "9", y2: "20" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleUnderline().run(), active: editor.isActive("underline"), title: "Underline (Ctrl+U)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("path", { d: "M6 3v7a6 6 0 006 6 6 6 0 006-6V3" }), /* @__PURE__ */ React.createElement("line", { x1: "4", y1: "21", x2: "20", y2: "21" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleStrike().run(), active: editor.isActive("strike"), title: "Strikethrough" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "4", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ React.createElement("path", { d: "M17.5 7.5c0-2-1.5-3.5-5.5-3.5S6.5 5.5 6.5 7.5c0 4 11 4 11 8 0 2-1.5 3.5-5.5 3.5s-5.5-1.5-5.5-3.5" }))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), active: editor.isActive("heading", { level: 2 }), title: "Heading 2" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "H2")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), active: editor.isActive("heading", { level: 3 }), title: "Heading 3" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "H3")), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBulletList().run(), active: editor.isActive("bulletList"), title: "Bullet list" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "6", x2: "20", y2: "6" }), /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ React.createElement("line", { x1: "9", y1: "18", x2: "20", y2: "18" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "6", r: "1", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "12", r: "1", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "18", r: "1", fill: "currentColor" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleOrderedList().run(), active: editor.isActive("orderedList"), title: "Numbered list" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "6", x2: "21", y2: "6" }), /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "12", x2: "21", y2: "12" }), /* @__PURE__ */ React.createElement("line", { x1: "10", y1: "18", x2: "21", y2: "18" }), /* @__PURE__ */ React.createElement("text", { x: "3", y: "8", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "1"), /* @__PURE__ */ React.createElement("text", { x: "3", y: "14", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "2"), /* @__PURE__ */ React.createElement("text", { x: "3", y: "20", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif" }, "3"))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleBlockquote().run(), active: editor.isActive("blockquote"), title: "Quote" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311C9.591 11.68 11 13.24 11 15.14c0 .94-.36 1.84-1.001 2.503A3.34 3.34 0 017.559 18.6a3.77 3.77 0 01-2.976-.879zm10.4 0C13.953 16.227 13.4 15 13.4 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.986.169 3.395 1.729 3.395 3.629 0 .94-.36 1.84-1.001 2.503a3.34 3.34 0 01-2.44.957 3.77 3.77 0 01-2.976-.879z" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleCodeBlock().run(), active: editor.isActive("codeBlock"), title: "Code block" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "16 18 22 12 16 6" }), /* @__PURE__ */ React.createElement("polyline", { points: "8 6 2 12 8 18" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().toggleCode().run(), active: editor.isActive("code"), title: "Inline code" }, /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[11px] font-bold" }, "{}")), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: addLink, active: editor.isActive("link"), title: "Link" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" }), /* @__PURE__ */ React.createElement("path", { d: "M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertImage, title: "Insert image" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), /* @__PURE__ */ React.createElement("polyline", { points: "21 15 16 10 5 21" }))), showComparisonTool ? /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertComparison, title: "Insert image comparison" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "4", width: "8", height: "16", rx: "1.5" }), /* @__PURE__ */ React.createElement("rect", { x: "13", y: "4", width: "8", height: "16", rx: "1.5" }), /* @__PURE__ */ React.createElement("path", { d: "M9 8h1M14 8h1M9 16h1M14 16h1" }))) : null, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertTable, title: "Insert table" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M3 9h18M3 15h18M9 3v18M15 3v18" }))), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().setHorizontalRule().run(), title: "Horizontal rule" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React.createElement("line", { x1: "3", y1: "12", x2: "21", y2: "12" }))), /* @__PURE__ */ React.createElement(EmojiPicker, { editor }), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().insertContent("@").run(), title: "Mention a user (type @username)" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "@")), advancedNews ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleSourceMode, active: activeSourceMode === "primary", title: sourceModeTitle, className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, sourceModeLabel)), secondarySourceModeLabel ? /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleSecondarySourceMode, active: activeSourceMode === "secondary", title: secondarySourceModeTitle, className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, secondarySourceModeLabel)) : null, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleStructureOutlines, active: showStructureOutlines, title: "Outline blocks (p, div, figure, list)", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "DOM")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertArtwork, title: "Embed artwork", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Art")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertSocialEmbed, title: "Embed social post", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Social")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertVideoEmbed, title: "Embed YouTube", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "YT")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onInsertHashtag, title: "Insert hashtag", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold" }, "#"))) : null, /* @__PURE__ */ React.createElement("div", { className: "ml-auto flex items-center gap-0.5" }, /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onDecreaseEditorViewportHeight, title: "Shorter editor", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "A-")), /* @__PURE__ */ React.createElement("div", { className: "mx-1 flex min-w-[5.25rem] items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, formatViewportHeightLabel(editorViewportHeight)), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onIncreaseEditorViewportHeight, title: "Taller editor", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "A+")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: onToggleFullHeightMode, active: fullHeightMode, title: fullHeightMode ? "Exit full height editor" : "Expand editor to full browser size", className: "w-auto px-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-bold uppercase tracking-[0.14em]" }, "Fit")), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().undo().run(), disabled: !editor.can().undo(), title: "Undo (Ctrl+Z)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "1 4 1 10 7 10" }), /* @__PURE__ */ React.createElement("path", { d: "M3.51 15a9 9 0 102.13-9.36L1 10" }))), /* @__PURE__ */ React.createElement(ToolbarBtn$2, { onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: "Redo (Ctrl+Shift+Z)" }, /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("polyline", { points: "23 4 23 10 17 10" }), /* @__PURE__ */ React.createElement("path", { d: "M20.49 15a9 9 0 11-2.13-9.36L23 10" }))))); } function RichTextEditor({ content: content2 = "", @@ -15181,16 +15550,26 @@ function RichTextEditor({ placeholder = "Write something…", error, minHeight = 12, + maxHeightRem = 42, autofocus = false, advancedNews = false, + sourceModeLabel = "HTML", + sourceModeTitle = "View or edit source HTML", + sourceModeDescription = "Edit the stored article HTML directly. Saving while in this mode keeps the HTML exactly as written here.", + secondarySourceModeLabel = null, + secondarySourceModeTitle = "", + secondarySourceModeDescription = "", + secondarySourceModeValue = null, + onSecondarySourceModeValueChange = null, searchEntities = null, mediaSupport = null }) { const viewportStorageKey = "rich-text-editor.viewport-height"; const viewportMinHeight = Math.max(minHeight + 6, 18); - const viewportMaxHeight = 42; + const viewportMaxHeight = Math.max(viewportMinHeight, Number(maxHeightRem) || 42); const viewportStep = 4; - const [sourceMode, setSourceMode] = reactExports.useState(false); + const [activeSourceMode, setActiveSourceMode] = reactExports.useState(null); + const [fullHeightMode, setFullHeightMode] = reactExports.useState(false); const [sourceValue, setSourceValue] = reactExports.useState(String(content2 || "")); const [showStructureOutlines, setShowStructureOutlines] = reactExports.useState(false); const [helperMessage, setHelperMessage] = reactExports.useState(""); @@ -15242,6 +15621,8 @@ function RichTextEditor({ return Math.min(viewportMaxHeight, viewportMinHeight); }); const editorRef = reactExports.useRef(null); + const resizeCleanupRef = reactExports.useRef(null); + const usesSecondarySourceMode = typeof onSecondarySourceModeValueChange === "function" && secondarySourceModeValue != null; const csrfToken2 = reactExports.useMemo(() => { if (typeof document === "undefined") { return ""; @@ -15549,7 +15930,7 @@ function RichTextEditor({ } }, onUpdate: ({ editor: currentEditor }) => { - if (!sourceMode) { + if (!activeSourceMode) { onChange?.(currentEditor.getHTML()); } } @@ -15572,49 +15953,132 @@ function RichTextEditor({ }, [academyAssetsOpen, academyAssetsPage, academyAssetsSearch, loadAcademyAssets]); reactExports.useEffect(() => { if (!editor) return; - if (sourceMode) return; + if (activeSourceMode) return; if ((content2 || "") === editor.getHTML()) return; editor.commands.setContent(content2 || "", false); const normalizedHtml = editor.getHTML(); if (normalizedHtml !== (content2 || "")) { onChange?.(normalizedHtml); } - }, [content2, editor, onChange, sourceMode]); + }, [activeSourceMode, content2, editor, onChange]); reactExports.useEffect(() => { - if (sourceMode) { + if (activeSourceMode === "primary") { setSourceValue(String(content2 || editor?.getHTML() || "")); } - }, [content2, editor, sourceMode]); + }, [activeSourceMode, content2, editor]); reactExports.useEffect(() => { if (typeof window === "undefined") { return; } window.localStorage.setItem(viewportStorageKey, String(editorViewportHeight)); }, [editorViewportHeight]); + const stopViewportResize = reactExports.useCallback(() => { + if (resizeCleanupRef.current) { + resizeCleanupRef.current(); + resizeCleanupRef.current = null; + } + if (typeof document !== "undefined") { + document.body.style.userSelect = ""; + document.body.style.cursor = ""; + } + }, []); + reactExports.useEffect(() => stopViewportResize, [stopViewportResize]); + reactExports.useEffect(() => { + if (!fullHeightMode || typeof window === "undefined") { + return void 0; + } + const previousOverflow = document.body.style.overflow; + const handleKeyDown2 = (event) => { + if (event.key === "Escape") { + setFullHeightMode(false); + } + }; + document.body.style.overflow = "hidden"; + window.addEventListener("keydown", handleKeyDown2); + return () => { + document.body.style.overflow = previousOverflow; + window.removeEventListener("keydown", handleKeyDown2); + }; + }, [fullHeightMode]); const decreaseEditorViewportHeight = reactExports.useCallback(() => { setEditorViewportHeight((current) => Math.max(viewportMinHeight, Number((current - viewportStep).toFixed(1)))); }, [viewportMinHeight, viewportStep]); const increaseEditorViewportHeight = reactExports.useCallback(() => { setEditorViewportHeight((current) => Math.min(viewportMaxHeight, Number((current + viewportStep).toFixed(1)))); }, [viewportMaxHeight, viewportStep]); - const resetEditorViewportHeight = reactExports.useCallback(() => { - setEditorViewportHeight(Math.min(viewportMaxHeight, viewportMinHeight)); - }, [viewportMaxHeight, viewportMinHeight]); + const toggleFullHeightMode = reactExports.useCallback(() => { + setFullHeightMode((current) => !current); + }, []); + const startViewportResize = reactExports.useCallback((event) => { + if (fullHeightMode || event.button !== 0 || typeof window === "undefined") { + return; + } + event.preventDefault(); + const startY = event.clientY; + const startHeight = editorViewportHeight; + const handlePointerMove = (moveEvent) => { + const deltaRem = (moveEvent.clientY - startY) / getRootFontSizePx(); + const nextHeight = Number((startHeight + deltaRem).toFixed(1)); + setEditorViewportHeight(Math.min(viewportMaxHeight, Math.max(viewportMinHeight, nextHeight))); + }; + const handlePointerUp = () => { + stopViewportResize(); + }; + window.addEventListener("pointermove", handlePointerMove); + window.addEventListener("pointerup", handlePointerUp); + window.addEventListener("pointercancel", handlePointerUp); + resizeCleanupRef.current = () => { + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", handlePointerUp); + window.removeEventListener("pointercancel", handlePointerUp); + }; + document.body.style.userSelect = "none"; + document.body.style.cursor = "ns-resize"; + }, [editorViewportHeight, fullHeightMode, stopViewportResize, viewportMaxHeight, viewportMinHeight]); const pushHelperMessage = reactExports.useCallback((message) => { setHelperMessage(message); }, []); + const commitPrimarySourceToEditor = reactExports.useCallback(() => { + if (editor) { + editor.commands.setContent(sourceValue || "", false); + } + }, [editor, sourceValue]); const handleToggleSourceMode = reactExports.useCallback(() => { - if (sourceMode) { - setSourceMode(false); - if (editor) { - editor.commands.setContent(sourceValue || "", false); - } + if (activeSourceMode === "primary") { + setActiveSourceMode(null); + commitPrimarySourceToEditor(); pushHelperMessage("Returned to visual editor."); return; } - setSourceValue(editor?.getHTML() || String(content2 || "")); - setSourceMode(true); - }, [content2, editor, pushHelperMessage, sourceMode, sourceValue]); + if (activeSourceMode === "secondary") { + setActiveSourceMode("primary"); + setSourceValue(String(content2 || editor?.getHTML() || "")); + return; + } + setSourceValue(String(content2 || editor?.getHTML() || "")); + setActiveSourceMode("primary"); + }, [activeSourceMode, commitPrimarySourceToEditor, content2, editor, pushHelperMessage]); + const handleToggleSecondarySourceMode = reactExports.useCallback(() => { + if (!usesSecondarySourceMode) { + return; + } + if (activeSourceMode === "secondary") { + setActiveSourceMode(null); + pushHelperMessage("Returned to visual editor."); + return; + } + if (activeSourceMode === "primary") { + commitPrimarySourceToEditor(); + } + setActiveSourceMode("secondary"); + }, [activeSourceMode, commitPrimarySourceToEditor, pushHelperMessage, usesSecondarySourceMode]); + const handleCloseSourceMode = reactExports.useCallback(() => { + if (activeSourceMode === "primary") { + commitPrimarySourceToEditor(); + } + setActiveSourceMode(null); + pushHelperMessage("Returned to visual editor."); + }, [activeSourceMode, commitPrimarySourceToEditor, pushHelperMessage]); const insertArtworkEmbed = reactExports.useCallback((item) => { if (!editor || !item) return; editor.chain().focus().insertContent({ @@ -15855,23 +16319,38 @@ function RichTextEditor({ if (!value) return; editor.chain().focus().insertContent(`#${value}`).run(); }, [editor]); - return /* @__PURE__ */ React.createElement("div", { className: "flex w-full min-w-0 flex-col gap-1.5" }, /* @__PURE__ */ React.createElement( + const shellClassName = fullHeightMode ? "fixed inset-0 z-[980] flex min-h-0 w-screen flex-col bg-[#04070df2] p-3 backdrop-blur-sm md:p-4" : "flex w-full min-w-0 flex-col gap-1.5"; + const bodyClassName = fullHeightMode ? "flex min-h-0 w-full flex-1 flex-col gap-1.5" : "flex w-full min-w-0 flex-col gap-1.5"; + const editorCardClassName = [ + "news-rich-text-editor w-full min-w-0 overflow-hidden rounded-xl border bg-white/[0.04] transition-colors", + fullHeightMode ? "flex min-h-0 flex-1 flex-col rounded-2xl" : "", + error ? "border-red-500/60 focus-within:border-red-500/70 focus-within:ring-2 focus-within:ring-red-500/30" : "border-white/12 hover:border-white/20 focus-within:border-sky-500/50 focus-within:ring-2 focus-within:ring-sky-500/20" + ].filter(Boolean).join(" "); + const editorViewportStyle = fullHeightMode ? { flex: 1 } : { height: `${editorViewportHeight}rem` }; + const sourceTextareaStyle = fullHeightMode ? { flex: 1 } : { + height: `${Math.max(minHeight, editorViewportHeight)}rem`, + minHeight: `${Math.max(minHeight, 20)}rem` + }; + return /* @__PURE__ */ React.createElement("div", { className: shellClassName }, /* @__PURE__ */ React.createElement("div", { className: bodyClassName }, /* @__PURE__ */ React.createElement( "div", { - className: [ - "news-rich-text-editor w-full min-w-0 overflow-hidden rounded-xl border bg-white/[0.04] transition-colors", - error ? "border-red-500/60 focus-within:border-red-500/70 focus-within:ring-2 focus-within:ring-red-500/30" : "border-white/12 hover:border-white/20 focus-within:border-sky-500/50 focus-within:ring-2 focus-within:ring-sky-500/20" - ].join(" ") + className: editorCardClassName }, /* @__PURE__ */ React.createElement( Toolbar$1, { editor, advancedNews, - sourceMode, + activeSourceMode, + sourceModeLabel, + sourceModeTitle, + secondarySourceModeLabel, + secondarySourceModeTitle, showStructureOutlines, showComparisonTool: Boolean(mediaSupport?.uploadUrl), + fullHeightMode, onToggleSourceMode: handleToggleSourceMode, + onToggleSecondarySourceMode: handleToggleSecondarySourceMode, onToggleStructureOutlines: () => setShowStructureOutlines((current) => !current), onInsertArtwork: handleInsertArtwork, onInsertImage: handleInsertImage, @@ -15883,27 +16362,46 @@ function RichTextEditor({ editorViewportHeight, onIncreaseEditorViewportHeight: increaseEditorViewportHeight, onDecreaseEditorViewportHeight: decreaseEditorViewportHeight, - onResetEditorViewportHeight: resetEditorViewportHeight + onToggleFullHeightMode: toggleFullHeightMode } ), - advancedNews && sourceMode ? /* @__PURE__ */ React.createElement("div", { className: "border-t border-white/[0.04] bg-black/10 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex items-center justify-between gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, "Edit the stored article HTML directly. Saving while in this mode keeps the HTML exactly as written here."), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleToggleSourceMode, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to visual")), /* @__PURE__ */ React.createElement( + advancedNews && activeSourceMode ? /* @__PURE__ */ React.createElement("div", { className: [ + "border-t border-white/[0.04] bg-black/10 px-4 py-3", + fullHeightMode ? "flex min-h-0 flex-1 flex-col" : "" + ].filter(Boolean).join(" ") }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex items-center justify-between gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, activeSourceMode === "secondary" ? secondarySourceModeDescription : sourceModeDescription), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleCloseSourceMode, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to visual")), /* @__PURE__ */ React.createElement( "textarea", { - value: sourceValue, + value: activeSourceMode === "secondary" ? String(secondarySourceModeValue || "") : sourceValue, onChange: (event) => { const nextValue = event.target.value; + if (activeSourceMode === "secondary") { + onSecondarySourceModeValueChange?.(nextValue); + return; + } setSourceValue(nextValue); onChange?.(nextValue); }, spellCheck: false, - className: "nova-scrollbar min-h-[20rem] w-full rounded-xl border border-white/10 bg-slate-950/85 px-4 py-3 font-mono text-sm leading-6 text-slate-100 outline-none", - style: { minHeight: `${Math.max(minHeight, 20)}rem` } + className: "nova-scrollbar w-full rounded-xl border border-white/10 bg-slate-950/85 px-4 py-3 font-mono text-sm leading-6 text-slate-100 outline-none", + style: sourceTextareaStyle } )) : /* @__PURE__ */ React.createElement("div", { className: [ "rich-text-editor-viewport nova-scrollbar w-full min-w-0 border-t border-white/[0.04] bg-black/15", + fullHeightMode ? "flex min-h-0 flex-1 flex-col" : "", advancedNews && showStructureOutlines ? "news-editor-outline" : "" - ].filter(Boolean).join(" "), style: { maxHeight: `${editorViewportHeight}rem` } }, /* @__PURE__ */ React.createElement(EditorContent, { editor })) - ), advancedNews && helperMessage ? /* @__PURE__ */ React.createElement("p", { className: "text-xs text-sky-300" }, helperMessage) : null, !sourceMode ? /* @__PURE__ */ React.createElement(RichTableControls, { editor }) : null, error ? /* @__PURE__ */ React.createElement("p", { role: "alert", className: "text-xs text-red-400" }, error) : null, /* @__PURE__ */ React.createElement( + ].filter(Boolean).join(" "), style: editorViewportStyle }, /* @__PURE__ */ React.createElement(EditorContent, { editor })), + !fullHeightMode ? /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + "aria-label": "Resize editor height", + title: "Drag to resize editor height", + onPointerDown: startViewportResize, + className: "group flex h-5 w-full cursor-row-resize items-center justify-center border-t border-white/[0.04] bg-black/10 text-slate-500 transition hover:bg-white/[0.03] hover:text-slate-300" + }, + /* @__PURE__ */ React.createElement("span", { className: "h-1 w-16 rounded-full bg-current opacity-70 transition group-hover:opacity-100" }) + ) : null + ), advancedNews && helperMessage ? /* @__PURE__ */ React.createElement("p", { className: "text-xs text-sky-300" }, helperMessage) : null, !activeSourceMode ? /* @__PURE__ */ React.createElement(RichTableControls, { editor }) : null, error ? /* @__PURE__ */ React.createElement("p", { role: "alert", className: "text-xs text-red-400" }, error) : null, /* @__PURE__ */ React.createElement( ArtworkPickerDialog, { open: artworkPickerOpen, @@ -15997,7 +16495,7 @@ function RichTextEditor({ onClose: () => setTableInsertOpen(false), onInsert: handleTableInsert } - )); + ))); } function formatBytes$2(bytes) { const value = Number(bytes || 0); @@ -16180,18 +16678,2217 @@ function WorldMediaUploadField({ ) )); } +const MONTH_NAMES = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" +]; +const DAY_ABBR = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; +function pad(value) { + return String(value).padStart(2, "0"); +} +function daysInMonth(year, month) { + return new Date(year, month + 1, 0).getDate(); +} +function firstWeekday(year, month) { + const day = new Date(year, month, 1).getDay(); + return (day + 6) % 7; +} +function toISODate(date) { + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; +} +function parseDatePart(value) { + if (!value) return null; + const [year, month, day] = value.split("-").map(Number); + if (!year || !month || !day) return null; + return new Date(year, month - 1, day); +} +function splitDateTime(value) { + if (!value) { + return { date: "", time: "" }; + } + const [date = "", time = ""] = String(value).split("T"); + return { + date, + time: time.slice(0, 5) + }; +} +function mergeDateTime(date, time) { + if (!date) return ""; + return `${date}T${time || "00:00"}`; +} +function maxDateValue(a, b2) { + if (!a) return b2 || ""; + if (!b2) return a || ""; + return a > b2 ? a : b2; +} +function minDateValue(a, b2) { + if (!a) return b2 || ""; + if (!b2) return a || ""; + return a < b2 ? a : b2; +} +function clampTimeToBounds(date, time, minDateTime, maxDateTime) { + const nextTime = time || "00:00"; + const minParts = splitDateTime(minDateTime); + const maxParts = splitDateTime(maxDateTime); + if (date && minParts.date === date && minParts.time && nextTime < minParts.time) { + return minParts.time; + } + if (date && maxParts.date === date && maxParts.time && nextTime > maxParts.time) { + return maxParts.time; + } + return nextTime; +} +function formatDisplay(value) { + if (!value) return ""; + const { date, time } = splitDateTime(value); + const parsed = parseDatePart(date); + if (!parsed) return ""; + return `${MONTH_NAMES[parsed.getMonth()].slice(0, 3)} ${parsed.getDate()}, ${parsed.getFullYear()}${time ? ` at ${time}` : ""}`; +} +function isSameDay(a, b2) { + return a?.getFullYear() === b2?.getFullYear() && a?.getMonth() === b2?.getMonth() && a?.getDate() === b2?.getDate(); +} +function CalendarGrid({ year, month, selectedDate, onSelect, minDate, maxDate }) { + const count = daysInMonth(year, month); + const start = firstWeekday(year, month); + const prevMonth = month - 1 < 0 ? 11 : month - 1; + const prevYear = month - 1 < 0 ? year - 1 : year; + const prevCount = daysInMonth(prevYear, prevMonth); + const cells = []; + for (let index2 = start - 1; index2 >= 0; index2 -= 1) { + cells.push({ + day: prevCount - index2, + current: false, + date: new Date(prevYear, prevMonth, prevCount - index2) + }); + } + for (let day = 1; day <= count; day += 1) { + cells.push({ day, current: true, date: new Date(year, month, day) }); + } + let nextDay = 1; + while (cells.length % 7 !== 0) { + cells.push({ day: nextDay, current: false, date: new Date(year, month + 1, nextDay) }); + nextDay += 1; + } + const today = /* @__PURE__ */ new Date(); + today.setHours(0, 0, 0, 0); + return /* @__PURE__ */ React.createElement("div", { className: "p-3" }, /* @__PURE__ */ React.createElement("div", { className: "mb-1 grid grid-cols-7" }, DAY_ABBR.map((day) => /* @__PURE__ */ React.createElement("div", { key: day, className: "py-1 text-center text-[10px] font-semibold text-slate-500" }, day))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-7 gap-y-0.5" }, cells.map((cell, index2) => { + const iso = toISODate(cell.date); + const selected = isSameDay(cell.date, selectedDate); + const todayCell = isSameDay(cell.date, today); + const disabled = minDate && iso < minDate || maxDate && iso > maxDate; + return /* @__PURE__ */ React.createElement( + "button", + { + key: `${iso}-${index2}`, + type: "button", + disabled, + onClick: () => onSelect(iso), + className: [ + "relative mx-auto flex h-8 w-8 items-center justify-center rounded-lg text-sm transition-all", + !cell.current ? "text-slate-600" : "", + cell.current && !selected && !disabled ? "text-white hover:bg-white/10" : "", + selected ? "bg-accent font-semibold text-white shadow shadow-accent/30" : "", + todayCell && !selected ? "text-accent ring-1 ring-accent/50" : "", + disabled ? "cursor-not-allowed opacity-30" : "cursor-pointer" + ].join(" ") + }, + cell.day + ); + }))); +} +function DateTimePicker({ + value = "", + onChange, + label, + placeholder, + error, + hint, + required = false, + clearable = false, + id, + disabled = false, + mode = "datetime", + minDate, + maxDate, + minDateTime, + maxDateTime, + className = "" +}) { + const today = /* @__PURE__ */ new Date(); + const initial = splitDateTime(value); + const initialDate = parseDatePart(initial.date) || today; + const [open, setOpen] = reactExports.useState(false); + const [dropPos, setDropPos] = reactExports.useState({ top: 0, left: 0, width: 320 }); + const [viewYear, setViewYear] = reactExports.useState(initialDate.getFullYear()); + const [viewMonth, setViewMonth] = reactExports.useState(initialDate.getMonth()); + const [draftDate, setDraftDate] = reactExports.useState(initial.date); + const nowTime = `${pad(today.getHours())}:${pad(today.getMinutes())}`; + const defaultDraftTime = (function() { + const baseDate = initial.date || toISODate(initialDate); + const candidate = initial.time || nowTime; + return clampTimeToBounds(baseDate, candidate, minDateTime, maxDateTime); + })(); + const [draftTime, setDraftTime] = reactExports.useState(defaultDraftTime); + const effectivePlaceholder = placeholder || (mode === "date" ? "Pick a date" : "Pick a date and time"); + const triggerRef = reactExports.useRef(null); + const inputId = id ?? (label ? `dtp-${label.toLowerCase().replace(/\s+/g, "-")}` : "date-time-picker"); + const panelId = `dtp-panel-${inputId}`; + reactExports.useEffect(() => { + const next = splitDateTime(value); + setDraftDate(next.date); + const fallbackTime = (() => { + const candidate = next.time || `${pad((/* @__PURE__ */ new Date()).getHours())}:${pad((/* @__PURE__ */ new Date()).getMinutes())}`; + const dateForClamp = next.date || toISODate(initialDate); + return clampTimeToBounds(dateForClamp, candidate, minDateTime, maxDateTime); + })(); + setDraftTime(next.time || fallbackTime); + const nextDate = parseDatePart(next.date); + if (nextDate) { + setViewYear(nextDate.getFullYear()); + setViewMonth(nextDate.getMonth()); + } + }, [value]); + const measure = reactExports.useCallback(() => { + if (!triggerRef.current) return; + const rect = triggerRef.current.getBoundingClientRect(); + const panelWidth = Math.max(rect.width, 320); + const panelHeight = 420; + const openUp = window.innerHeight - rect.bottom < panelHeight + 8 && rect.top > panelHeight + 8; + setDropPos({ + top: openUp ? rect.top - panelHeight - 4 : rect.bottom + 4, + left: Math.min(rect.left, window.innerWidth - panelWidth - 8), + width: panelWidth + }); + }, []); + const openPicker = reactExports.useCallback(() => { + if (disabled) return; + measure(); + setOpen(true); + }, [disabled, measure]); + reactExports.useEffect(() => { + if (!open) return void 0; + const handleMouseDown = (event) => { + if (!triggerRef.current?.contains(event.target) && !document.getElementById(panelId)?.contains(event.target)) { + setOpen(false); + } + }; + document.addEventListener("mousedown", handleMouseDown); + return () => document.removeEventListener("mousedown", handleMouseDown); + }, [open, panelId]); + reactExports.useEffect(() => { + if (!open) return void 0; + const handleScroll = (event) => { + if (document.getElementById(panelId)?.contains(event.target)) return; + setOpen(false); + }; + const handleResize = () => setOpen(false); + window.addEventListener("scroll", handleScroll, true); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("scroll", handleScroll, true); + window.removeEventListener("resize", handleResize); + }; + }, [open, panelId]); + const applyValue = reactExports.useCallback((date, time) => { + if (!date) { + onChange?.(""); + return; + } + onChange?.(mode === "date" ? date : mergeDateTime(date, time)); + }, [mode, onChange]); + const handleDateSelect = (nextDate) => { + const nextTime = clampTimeToBounds(nextDate, draftTime, minDateTime, maxDateTime); + setDraftDate(nextDate); + setDraftTime(nextTime); + applyValue(nextDate, nextTime); + }; + const handleTimeChange = (event) => { + const nextTime = clampTimeToBounds(draftDate, event.target.value, minDateTime, maxDateTime); + setDraftTime(nextTime); + applyValue(draftDate, nextTime); + }; + const clearValue = (event) => { + event.stopPropagation(); + const now = `${pad((/* @__PURE__ */ new Date()).getHours())}:${pad((/* @__PURE__ */ new Date()).getMinutes())}`; + setDraftDate(""); + setDraftTime(now); + onChange?.(""); + }; + const prevMonth = () => { + if (viewMonth === 0) { + setViewMonth(11); + setViewYear((current) => current - 1); + return; + } + setViewMonth((current) => current - 1); + }; + const nextMonth = () => { + if (viewMonth === 11) { + setViewMonth(0); + setViewYear((current) => current + 1); + return; + } + setViewMonth((current) => current + 1); + }; + const triggerClass = [ + "relative flex h-[42px] w-full cursor-pointer items-center gap-2 rounded-xl border px-3.5 text-sm transition-all duration-150", + "bg-white/[0.06] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0", + error ? "border-red-500/60 focus-visible:ring-red-500/40" : open ? "border-accent/50 ring-2 ring-accent/40" : "border-white/12 hover:border-white/22", + disabled ? "pointer-events-none cursor-not-allowed opacity-50" : "", + className + ].join(" "); + const selectedDate = parseDatePart(draftDate); + const minDateTimeParts = splitDateTime(minDateTime); + const maxDateTimeParts = splitDateTime(maxDateTime); + const effectiveMinDate = maxDateValue(minDate, minDateTimeParts.date); + const effectiveMaxDate = minDateValue(maxDate, maxDateTimeParts.date); + const minTime = draftDate && draftDate === minDateTimeParts.date ? minDateTimeParts.time || void 0 : void 0; + const maxTime = draftDate && draftDate === maxDateTimeParts.date ? maxDateTimeParts.time || void 0 : void 0; + return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1.5" }, label && /* @__PURE__ */ React.createElement("label", { htmlFor: inputId, className: "text-sm font-medium text-white/85 select-none" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "ml-1 text-red-400" }, "*")), /* @__PURE__ */ React.createElement( + "div", + { + ref: triggerRef, + id: inputId, + role: "button", + tabIndex: disabled ? -1 : 0, + "aria-label": label ?? effectivePlaceholder, + className: triggerClass, + onClick: openPicker, + onKeyDown: (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + openPicker(); + } + } + }, + /* @__PURE__ */ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", className: "shrink-0 text-slate-500", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { x: "1", y: "2.5", width: "12", height: "10.5", rx: "1.5", stroke: "currentColor", strokeWidth: "1.3" }), /* @__PURE__ */ React.createElement("path", { d: "M1 6h12", stroke: "currentColor", strokeWidth: "1.3" }), /* @__PURE__ */ React.createElement("path", { d: "M4 1v3M10 1v3", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }), /* @__PURE__ */ React.createElement("circle", { cx: "4.5", cy: "9", r: "0.75", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "7", cy: "9", r: "0.75", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "9.5", cy: "9", r: "0.75", fill: "currentColor" })), + /* @__PURE__ */ React.createElement("span", { className: `flex-1 truncate ${value ? "text-white" : "text-slate-500"}` }, value ? formatDisplay(value) : effectivePlaceholder), + clearable && value && /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + tabIndex: -1, + onClick: clearValue, + className: "flex h-5 w-5 items-center justify-center rounded text-slate-500 transition-colors hover:text-white", + "aria-label": "Clear date and time" + }, + /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M1 1l8 8M9 1L1 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })) + ) + ), error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "text-xs text-red-400" }, error), !error && hint && /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500" }, hint), open && reactDomExports.createPortal( + /* @__PURE__ */ React.createElement( + "div", + { + id: panelId, + className: "fixed z-[500] overflow-hidden rounded-2xl border border-white/12 bg-nova-900 shadow-2xl shadow-black/50", + style: { top: dropPos.top, left: dropPos.left, width: dropPos.width } + }, + /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between px-3 pt-3" }, /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: prevMonth, + className: "flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white", + "aria-label": "Previous month" + }, + /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M7 1L3 5l4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })) + ), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, MONTH_NAMES[viewMonth], " ", viewYear), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: nextMonth, + className: "flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-white/8 hover:text-white", + "aria-label": "Next month" + }, + /* @__PURE__ */ React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M3 1l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })) + )), + /* @__PURE__ */ React.createElement( + CalendarGrid, + { + year: viewYear, + month: viewMonth, + selectedDate, + onSelect: handleDateSelect, + minDate: effectiveMinDate, + maxDate: effectiveMaxDate + } + ), + /* @__PURE__ */ React.createElement("div", { className: "border-t border-white/8 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: `grid gap-3 ${mode === "date" ? "" : "sm:grid-cols-[minmax(0,1fr)_7rem] sm:items-end"}` }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Selected date"), /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-sm text-white" }, draftDate ? formatDisplay(draftDate) : "Pick a day")), mode !== "date" ? /* @__PURE__ */ React.createElement("label", { className: "grid gap-1.5 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Time"), /* @__PURE__ */ React.createElement( + "input", + { + type: "time", + value: draftTime, + onChange: handleTimeChange, + min: minTime, + max: maxTime, + className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-white outline-none transition focus:border-accent/50 focus:ring-2 focus:ring-accent/40" + } + )) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex items-center justify-between" }, /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => handleDateSelect(toISODate(/* @__PURE__ */ new Date())), + className: "text-xs font-medium text-accent transition-colors hover:text-accent/80" + }, + "Today" + ), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => setOpen(false), + className: "rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-medium text-white transition hover:bg-white/[0.08]" + }, + "Done" + ))) + ), + document.body + )); +} +const COURSE_EDITOR_TABS = [ + { + id: "overview", + label: "Overview", + description: "Title, slug, positioning, and the short summary shown on course cards.", + icon: "fa-compass-drafting", + sections: ["course-identity"] + }, + { + id: "content", + label: "Content", + description: "Use the richer WYSIWYG surface for the main course description and learning pitch.", + icon: "fa-pen-nib", + sections: ["course-description"] + }, + { + id: "media", + label: "Media", + description: "Upload and tune the cover and teaser visuals used across the public course surfaces.", + icon: "fa-images", + sections: ["course-media"] + }, + { + id: "lessons", + label: "Lessons", + description: "Build the lesson sequence, drag to reorder, and add or remove lessons without opening the full builder.", + icon: "fa-list-ol", + sections: ["course-lessons-manager"] + }, + { + id: "publish", + label: "Publish", + description: "Control access, status, ordering, scheduling, and featured placement.", + icon: "fa-rocket-launch", + sections: ["course-publishing", "course-seo"] + }, + { + id: "preview", + label: "Preview", + description: "Scan the public-facing course card, media, and rendered long description before publishing.", + icon: "fa-eye", + sections: ["course-preview"] + } +]; +const COURSE_FIELD_TAB_MAP = { + title: "overview", + slug: "overview", + subtitle: "overview", + excerpt: "overview", + description: "content", + cover_image: "media", + teaser_image: "media", + access_level: "publish", + difficulty: "publish", + status: "publish", + order_num: "publish", + estimated_minutes: "publish", + published_at: "publish", + is_featured: "publish", + seo_title: "publish", + seo_description: "publish", + meta_keywords: "publish", + og_title: "publish", + og_description: "publish", + og_image: "publish" +}; +function getField$2(fields, name2) { + return fields.find((field) => field.name === name2) || null; +} +function FieldError$3({ message }) { + if (!message) return null; + return /* @__PURE__ */ React.createElement("p", { className: "text-xs text-rose-300" }, message); +} +function SectionCard$6({ id, eyebrow, title, description, actions, children, tone = "default", className = "", contentClassName = "" }) { + const toneClass = tone === "feature" ? "bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.92))] shadow-[0_24px_70px_rgba(2,6,23,0.28)]" : "bg-white/[0.03]"; + return /* @__PURE__ */ React.createElement("section", { id, className: `min-w-0 scroll-mt-24 rounded-[28px] border border-white/10 p-5 ${toneClass} ${className}`.trim() }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, eyebrow ? /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/75" }, eyebrow) : null, /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold tracking-[-0.03em] text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, description) : null), actions ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, actions) : null), /* @__PURE__ */ React.createElement("div", { className: `mt-5 ${contentClassName}`.trim() }, children)); +} +function EditorWorkspaceTabs$1({ tabs, activeTab, onChange, errorCounts }) { + const activeMeta = tabs.find((tab2) => tab2.id === activeTab) || tabs[0]; + return /* @__PURE__ */ React.createElement("div", { className: "sticky top-4 z-20 rounded-[24px] border border-white/10 bg-[linear-gradient(180deg,rgba(7,11,18,0.92),rgba(5,8,14,0.88))] px-3 py-3 shadow-[0_18px_50px_rgba(2,6,23,0.18)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2", role: "tablist", "aria-label": "Course editor sections" }, tabs.map((tab2) => { + const isActive = tab2.id === activeTab; + const errorCount = Number(errorCounts?.[tab2.id] || 0); + return /* @__PURE__ */ React.createElement( + "button", + { + key: tab2.id, + type: "button", + role: "tab", + "aria-selected": isActive, + "aria-controls": `course-editor-panel-${tab2.id}`, + id: `course-editor-tab-${tab2.id}`, + onClick: () => onChange(tab2.id), + className: [ + "inline-flex items-center gap-2 rounded-2xl border px-4 py-2.5 text-sm font-semibold transition", + isActive ? "border-sky-300/25 bg-sky-300/12 text-sky-100 ring-1 ring-sky-300/20" : "border-white/10 bg-white/[0.03] text-white/80 hover:border-sky-300/30 hover:bg-sky-300/10 hover:text-white" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${tab2.icon} text-xs` }), + /* @__PURE__ */ React.createElement("span", null, tab2.label), + errorCount > 0 ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.14em] text-rose-100" }, errorCount) : null + ); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap items-center justify-between gap-3 px-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-6 text-slate-400" }, activeMeta.description), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.16em] text-slate-500" }, activeMeta.sections.map((section) => /* @__PURE__ */ React.createElement("span", { key: section, className: "rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5" }, section.replace("course-", "").replace(/-/g, " ")))))); +} +function TextField$3({ label, value, onChange, error, hint, ...rest }) { + return /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("input", { value: value ?? "", onChange, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none", ...rest }), hint ? /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, hint) : null, /* @__PURE__ */ React.createElement(FieldError$3, { message: error })); +} +function TextAreaField$2({ label, value, onChange, error, rows = 4, hint }) { + return /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("textarea", { value: value ?? "", onChange, rows, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), hint ? /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, hint) : null, /* @__PURE__ */ React.createElement(FieldError$3, { message: error })); +} +function CheckboxCardField({ label, checked, onChange, description, error }) { + return /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer items-start gap-4 rounded-[28px] border px-5 py-4 transition ${checked ? "border-[#f39a24]/35 bg-[#f39a24]/10" : "border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "sr-only" }), /* @__PURE__ */ React.createElement("span", { className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border text-sm transition ${checked ? "border-[#f39a24] bg-[#f39a24] text-white" : "border-white/10 bg-[#151a29] text-transparent"}` }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-check" })), /* @__PURE__ */ React.createElement("span", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block text-lg font-semibold tracking-[-0.02em] text-white" }, label), description ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-sm leading-6 text-slate-300" }, description) : null, /* @__PURE__ */ React.createElement(FieldError$3, { message: error }))); +} +function OutlineSectionPill({ section }) { + return /* @__PURE__ */ React.createElement("div", { className: "rounded-[20px] border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("p", { className: "truncate text-sm font-semibold text-white" }, section.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-[11px] uppercase tracking-[0.16em] text-slate-500" }, section.is_visible ? "Visible section" : "Hidden section")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold text-slate-200" }, section.lesson_count))); +} +function slugifyCourseTitle(value) { + return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 180); +} +function formatLessonStep(orderNum) { + const numeric = Number(orderNum); + if (!Number.isFinite(numeric) || numeric < 0) return null; + return `Step ${String(numeric + 1).padStart(2, "0")}`; +} +function normalizeLessonManagerLessons(lessons) { + return (Array.isArray(lessons) ? [...lessons] : []).sort((a, b2) => { + const diff = Number(a?.order_num || 0) - Number(b2?.order_num || 0); + return diff !== 0 ? diff : Number(a?.id || 0) - Number(b2?.id || 0); + }).map((lesson, index2) => ({ ...lesson, order_num: index2, display_order: index2 + 1 })); +} +function reorderLessonManagerLessons(lessons, draggedId, targetId) { + const current = normalizeLessonManagerLessons(lessons); + const di = current.findIndex((l3) => Number(l3.id) === Number(draggedId)); + const ti = current.findIndex((l3) => Number(l3.id) === Number(targetId)); + if (di === -1 || ti === -1 || di === ti) return current; + const next = [...current]; + const [moved] = next.splice(di, 1); + next.splice(ti, 0, moved); + return normalizeLessonManagerLessons(next); +} +function moveLessonManagerLesson(lessons, lessonId, direction) { + const current = normalizeLessonManagerLessons(lessons); + const idx = current.findIndex((l3) => Number(l3.id) === Number(lessonId)); + const nextIdx = idx + direction; + if (idx === -1 || nextIdx < 0 || nextIdx >= current.length) return current; + const next = [...current]; + const [moved] = next.splice(idx, 1); + next.splice(nextIdx, 0, moved); + return normalizeLessonManagerLessons(next); +} +function lessonManagerSignature(lessons) { + return JSON.stringify(normalizeLessonManagerLessons(lessons).map((l3) => ({ + id: Number(l3.id), + order_num: Number(l3.order_num || 0), + section_id: l3.section_id == null ? null : Number(l3.section_id) + }))); +} +function stripHtml$5(value) { + return String(value || "").replace(//gi, " ").replace(//gi, " ").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim(); +} +function countWords$1(value) { + const text2 = stripHtml$5(value); + return text2 ? text2.split(/\s+/).length : 0; +} +function normalizeAssetPreview(value, cdnBaseUrl) { + const trimmed = String(value || "").trim(); + if (!trimmed) return ""; + if (trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("/")) return trimmed; + return `${String(cdnBaseUrl || "").replace(/\/$/, "")}/${trimmed.replace(/^\//, "")}`; +} +function firstCourseErrorTab(errors) { + const firstKey = Object.keys(errors || {})[0]; + if (!firstKey) return null; + return COURSE_FIELD_TAB_MAP[firstKey] || null; +} +function courseTabErrorCounts(errors) { + const counts = {}; + Object.keys(errors || {}).forEach((key) => { + const tabId = COURSE_FIELD_TAB_MAP[key]; + if (!tabId) return; + counts[tabId] = Number(counts[tabId] || 0) + 1; + }); + return counts; +} +function renderMetaKeywords(value) { + return String(value || "").split(/[\n,]/).map((item) => item.trim()).filter(Boolean).slice(0, 6); +} +function CourseEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method, editorContext = {} }) { + const form = G$1({ + ...record, + description: String(record.description || ""), + cover_image: String(record.cover_image || ""), + teaser_image: String(record.teaser_image || "") + }); + const slugTouchedRef = reactExports.useRef(Boolean(String(record.slug || "").trim())); + const [activeTab, setActiveTab] = reactExports.useState("overview"); + const [coverPreviewUrl, setCoverPreviewUrl] = reactExports.useState(record.cover_image_url || normalizeAssetPreview(record.cover_image, editorContext.coverCdnBaseUrl)); + const [teaserPreviewUrl, setTeaserPreviewUrl] = reactExports.useState(record.teaser_image_url || normalizeAssetPreview(record.teaser_image, editorContext.coverCdnBaseUrl)); + const [stagedCoverPath, setStagedCoverPath] = reactExports.useState(""); + const [stagedTeaserPath, setStagedTeaserPath] = reactExports.useState(""); + const accessField = reactExports.useMemo(() => getField$2(fields, "access_level"), [fields]); + const difficultyField = reactExports.useMemo(() => getField$2(fields, "difficulty"), [fields]); + const statusField = reactExports.useMemo(() => getField$2(fields, "status"), [fields]); + const wordCount = reactExports.useMemo(() => countWords$1(form.data.description), [form.data.description]); + const excerptLength = String(form.data.excerpt || "").length; + const tabErrorCounts = reactExports.useMemo(() => courseTabErrorCounts(form.errors), [form.errors]); + const deferredDescription = reactExports.useDeferredValue(form.data.description || ""); + const visibleSections = reactExports.useMemo(() => new Set(COURSE_EDITOR_TABS.find((tab2) => tab2.id === activeTab)?.sections || []), [activeTab]); + const activeTabMeta = reactExports.useMemo(() => COURSE_EDITOR_TABS.find((tab2) => tab2.id === activeTab) || COURSE_EDITOR_TABS[0], [activeTab]); + const sectionClassName = (sectionId, className = "") => `${visibleSections.has(sectionId) ? "" : "hidden"} ${className}`.trim(); + const editorLinks = editorContext?.links || {}; + const outlineSummary = editorContext?.outlineSummary || null; + const coursePathPreview = form.data.slug ? `/academy/courses/${form.data.slug}` : "/academy/courses/course-slug"; + const metaKeywordItems = renderMetaKeywords(form.data.meta_keywords); + const attachLessonUrl = editorContext?.attachLessonUrl || null; + const reorderUrl = editorContext?.reorderUrl || null; + const courseLessonsSource = reactExports.useMemo(() => Array.isArray(editorContext?.courseLessons) ? editorContext.courseLessons : [], [editorContext]); + const availableLessons = reactExports.useMemo(() => Array.isArray(editorContext?.availableLessons) ? editorContext.availableLessons : [], [editorContext]); + const [lessonManagerDraft, setLessonManagerDraft] = reactExports.useState(() => normalizeLessonManagerLessons(Array.isArray(editorContext?.courseLessons) ? editorContext.courseLessons : [])); + const [lessonDragActive, setLessonDragActive] = reactExports.useState(null); + const [lessonSaveProcessing, setLessonSaveProcessing] = reactExports.useState(false); + const [lessonSearch, setLessonSearch] = reactExports.useState(""); + const lessonManagerIsDirty = reactExports.useMemo(() => lessonManagerSignature(lessonManagerDraft) !== lessonManagerSignature(courseLessonsSource), [lessonManagerDraft, courseLessonsSource]); + const filteredAvailableLessons = reactExports.useMemo(() => { + const q2 = lessonSearch.trim().toLowerCase(); + const unattached = availableLessons.filter((l3) => !l3.attached); + if (!q2) return unattached; + return unattached.filter((l3) => l3.title.toLowerCase().includes(q2) || l3.category.toLowerCase().includes(q2)); + }, [availableLessons, lessonSearch]); + reactExports.useEffect(() => { + setLessonManagerDraft(normalizeLessonManagerLessons(courseLessonsSource)); + }, [courseLessonsSource]); + reactExports.useEffect(() => { + if (slugTouchedRef.current) return; + form.setData("slug", slugifyCourseTitle(form.data.title)); + }, [form, form.data.title]); + reactExports.useEffect(() => { + const nextTab = firstCourseErrorTab(form.errors); + if (!nextTab) return; + setActiveTab(nextTab); + }, [form.errors]); + const handleManualCoverChange = (nextValue) => { + setStagedCoverPath(""); + form.setData("cover_image", nextValue); + setCoverPreviewUrl(normalizeAssetPreview(nextValue, editorContext.coverCdnBaseUrl)); + }; + const attachLesson = (lesson) => { + if (!attachLessonUrl) return; + At.post(attachLessonUrl, { + lesson_id: lesson.id, + order_num: courseLessonsSource.length, + is_required: true + }, { preserveScroll: true }); + }; + const detachLesson = (courseLesson) => { + if (!courseLesson.destroy_url) return; + if (!window.confirm(`Remove "${courseLesson.title}" from this course?`)) return; + At.delete(courseLesson.destroy_url, { preserveScroll: true }); + }; + const saveLessonOrder = () => { + if (!reorderUrl) return; + setLessonSaveProcessing(true); + At.patch(reorderUrl, { + sections: [], + lessons: lessonManagerDraft.map((l3) => ({ + id: l3.id, + order_num: l3.order_num, + section_id: l3.section_id ?? null + })) + }, { + preserveScroll: true, + onFinish: () => setLessonSaveProcessing(false) + }); + }; + const handleManualTeaserChange = (nextValue) => { + setStagedTeaserPath(""); + form.setData("teaser_image", nextValue); + setTeaserPreviewUrl(normalizeAssetPreview(nextValue, editorContext.coverCdnBaseUrl)); + }; + const submit = (event) => { + event.preventDefault(); + if (method === "patch") { + form.patch(submitUrl); + return; + } + form.post(submitUrl); + }; + const deleteCourse = () => { + if (!destroyUrl) return; + if (!window.confirm("Delete this course?")) return; + At.delete(destroyUrl); + }; + return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${title}` }), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-6 pb-16" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_34%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.94))] shadow-[0_24px_70px_rgba(2,6,23,0.34)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-white/10 px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white transition hover:bg-white/[0.08]" }, "Back to courses"), /* @__PURE__ */ React.createElement("span", null, destroyUrl ? "Edit course" : "New course")), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.05em] text-white" }, form.data.title || "Untitled academy course"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-7 text-slate-300" }, "Design the course like a polished editorial landing page: keep the structure clear, use the rich description editor, and upload visuals that look intentional on the public cards and hero.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, editorLinks.builder ? /* @__PURE__ */ React.createElement(xe, { href: editorLinks.builder, className: "rounded-2xl border border-amber-300/20 bg-amber-300/10 px-4 py-2.5 text-sm font-semibold text-amber-100 transition hover:brightness-110" }, "Open builder") : null, editorLinks.preview ? /* @__PURE__ */ React.createElement(xe, { href: editorLinks.preview, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Preview public page") : null, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-2xl border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save course")))), /* @__PURE__ */ React.createElement(EditorWorkspaceTabs$1, { tabs: COURSE_EDITOR_TABS, activeTab, onChange: setActiveTab, errorCounts: tabErrorCounts }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.14)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Current workspace"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, activeTabMeta.label), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-6 text-slate-400" }, activeTabMeta.description)), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Words"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, wordCount.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Excerpt"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, excerptLength, "/800")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Errors"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Object.keys(form.errors || {}).length))))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6", role: "tabpanel", id: `course-editor-panel-${activeTab}`, "aria-labelledby": `course-editor-tab-${activeTab}` }, /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-identity", eyebrow: "Positioning", title: "Identity and summary", description: "Start with the public-facing identity shown on the course index, hero, and internal Academy modules.", tone: "feature", className: sectionClassName("course-identity") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Title", + value: form.data.title, + onChange: (event) => form.setData("title", event.target.value), + error: form.errors.title, + maxLength: 180, + placeholder: "AI-Assisted Digital Art Foundations" + } + ), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + slugTouchedRef.current = false; + form.setData("slug", slugifyCourseTitle(form.data.title)); + }, className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold text-white" }, "Sync")), /* @__PURE__ */ React.createElement( + "input", + { + value: form.data.slug, + onChange: (event) => { + slugTouchedRef.current = String(event.target.value).trim() !== ""; + form.setData("slug", event.target.value); + }, + className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none", + placeholder: "ai-assisted-digital-art-foundations", + maxLength: 180 + } + ), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "The public course URL updates from the title until you override it."), /* @__PURE__ */ React.createElement(FieldError$3, { message: form.errors.slug }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Subtitle", + value: form.data.subtitle, + onChange: (event) => form.setData("subtitle", event.target.value), + error: form.errors.subtitle, + maxLength: 255, + placeholder: "A guided path for Skinbase creators" + } + ), /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Estimated minutes", + value: form.data.estimated_minutes ?? "", + onChange: (event) => form.setData("estimated_minutes", event.target.value), + error: form.errors.estimated_minutes, + type: "number", + min: "1", + placeholder: "90", + hint: "Shown on public course cards and the course hero." + } + )), /* @__PURE__ */ React.createElement( + TextAreaField$2, + { + label: "Excerpt", + value: form.data.excerpt, + onChange: (event) => form.setData("excerpt", event.target.value), + error: form.errors.excerpt, + rows: 5, + hint: "Keep this tight and outcome-focused. This summary is reused on cards, related modules, and SEO helpers." + } + )), /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-description", eyebrow: "Long-form content", title: "Course description", description: "Use the same richer WYSIWYG surface as lessons so the course page can carry structured copy, lists, and supporting media.", tone: "feature", className: sectionClassName("course-description") }, /* @__PURE__ */ React.createElement( + RichTextEditor, + { + content: form.data.description, + onChange: (nextHtml) => form.setData("description", nextHtml), + placeholder: "Explain what the course covers, who it is for, what workflows it teaches, and why a Skinbase creator should follow this path from start to finish.", + error: form.errors.description, + minHeight: 24, + maxHeightRem: 42, + autofocus: false, + advancedNews: true, + mediaSupport: { + uploadUrl: editorContext.bodyMediaUploadUrl, + deleteUrl: editorContext.bodyMediaDeleteUrl, + assetsUrl: editorContext.bodyMediaAssetsUrl, + slot: "body" + } + } + )), /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-media", eyebrow: "Visual system", title: "Cover and teaser media", description: "Upload clean landscape images that work across the featured course rail, the course index cards, and the public course hero.", className: sectionClassName("course-media") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement( + WorldMediaUploadField, + { + label: "Cover image", + slot: "cover", + value: form.data.cover_image, + previewUrl: coverPreviewUrl, + emptyLabel: "Course cover", + helperText: "Preferred 1600×900 at 16:9. Minimum upload is 1200×630. Use this as the main hero image for the course page and featured cards.", + uploadUrl: editorContext.coverUploadUrl, + deleteUrl: editorContext.coverDeleteUrl, + isTemporaryValue: Boolean(stagedCoverPath) && stagedCoverPath === form.data.cover_image, + onChange: ({ path, url }) => { + setStagedCoverPath(path || ""); + form.setData("cover_image", path || ""); + setCoverPreviewUrl(url || normalizeAssetPreview(path || "", editorContext.coverCdnBaseUrl)); + } + } + ), /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Cover image path override", + value: form.data.cover_image, + onChange: (event) => handleManualCoverChange(event.target.value), + error: form.errors.cover_image, + placeholder: "academy/lessons/covers/..." + } + )), /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement( + WorldMediaUploadField, + { + label: "Teaser image", + slot: "cover", + value: form.data.teaser_image, + previewUrl: teaserPreviewUrl, + emptyLabel: "Course teaser", + helperText: "Preferred 1600×900 at 16:9. Use this as the lighter secondary image for index cards or fallback thumbnail treatment when the main cover is too dense.", + uploadUrl: editorContext.coverUploadUrl, + deleteUrl: editorContext.coverDeleteUrl, + isTemporaryValue: Boolean(stagedTeaserPath) && stagedTeaserPath === form.data.teaser_image, + onChange: ({ path, url }) => { + setStagedTeaserPath(path || ""); + form.setData("teaser_image", path || ""); + setTeaserPreviewUrl(url || normalizeAssetPreview(path || "", editorContext.coverCdnBaseUrl)); + } + } + ), /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Teaser image path override", + value: form.data.teaser_image, + onChange: (event) => handleManualTeaserChange(event.target.value), + error: form.errors.teaser_image, + placeholder: "academy/lessons/covers/..." + } + ))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/18 bg-sky-300/8 px-4 py-4 text-sm leading-7 text-slate-300" }, "The public course index and the course hero both render landscape imagery first. If you only prepare one asset, prioritize the cover image. If you prepare both, keep them in the same visual family so the course feels consistent across list and detail pages.")), /* @__PURE__ */ React.createElement( + SectionCard$6, + { + id: "course-lessons-manager", + eyebrow: "Lesson sequence", + title: "Manage course lessons", + description: "Add lessons from the library, drag rows to reorder, use the arrows for precision, and save the updated sequence. Removing a lesson detaches it from this course immediately.", + tone: "feature", + className: sectionClassName("course-lessons-manager"), + actions: editorLinks.builder ? /* @__PURE__ */ React.createElement("a", { href: editorLinks.builder, className: "rounded-2xl border border-amber-300/20 bg-amber-300/10 px-4 py-2.5 text-sm font-semibold text-amber-100 transition hover:brightness-110" }, "Open full builder") : null + }, + /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Lesson sequence", lessonManagerDraft.length > 0 ? /* @__PURE__ */ React.createElement("span", { className: "ml-2 rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] text-slate-300" }, lessonManagerDraft.length) : null, lessonManagerIsDirty ? /* @__PURE__ */ React.createElement("span", { className: "ml-2 rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-0.5 text-[10px] text-amber-200" }, "Unsaved order") : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => setLessonManagerDraft(normalizeLessonManagerLessons(courseLessonsSource)), + disabled: !lessonManagerIsDirty || lessonSaveProcessing, + className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white disabled:opacity-40" + }, + "Reset order" + ), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: saveLessonOrder, + disabled: !lessonManagerIsDirty || lessonSaveProcessing, + className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-3 py-1.5 text-xs font-semibold text-sky-100 disabled:opacity-40" + }, + lessonSaveProcessing ? "Saving…" : "Save order" + ))), lessonManagerDraft.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.03] px-5 py-6 text-sm text-slate-400" }, "No lessons attached to this course yet. Add lessons from the library below.") : lessonManagerDraft.map((lesson, lessonIndex) => /* @__PURE__ */ React.createElement( + "div", + { + key: lesson.id, + draggable: true, + onDragStart: () => setLessonDragActive({ id: lesson.id }), + onDragEnd: () => setLessonDragActive(null), + onDragOver: (e) => e.preventDefault(), + onDrop: (e) => { + e.preventDefault(); + if (!lessonDragActive) return; + setLessonManagerDraft(reorderLessonManagerLessons(lessonManagerDraft, lessonDragActive.id, lesson.id)); + setLessonDragActive(null); + }, + className: [ + "flex flex-wrap items-center justify-between gap-3 rounded-2xl border px-4 py-3 transition", + "border-white/10 bg-white/[0.03] cursor-grab", + lessonDragActive && Number(lessonDragActive.id) === Number(lesson.id) ? "opacity-50 border-sky-300/30" : "" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("div", { className: "flex min-w-0 items-center gap-3" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-grip-vertical text-xs text-slate-600" }), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, formatLessonStep(lesson.order_num) || `#${lesson.display_order}`), lesson.formatted_lesson_number ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, lesson.formatted_lesson_number) : null, lesson.section_title ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, lesson.section_title) : null, lesson.difficulty ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, lesson.difficulty) : null), /* @__PURE__ */ React.createElement("p", { className: "mt-1.5 truncate text-sm font-semibold text-white" }, lesson.title))), + /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => setLessonManagerDraft(moveLessonManagerLesson(lessonManagerDraft, lesson.id, -1)), + disabled: lessonIndex === 0, + className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1.5 text-xs font-semibold text-white disabled:opacity-30", + title: "Move up" + }, + /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up" }) + ), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => setLessonManagerDraft(moveLessonManagerLesson(lessonManagerDraft, lesson.id, 1)), + disabled: lessonIndex === lessonManagerDraft.length - 1, + className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1.5 text-xs font-semibold text-white disabled:opacity-30", + title: "Move down" + }, + /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-down" }) + ), lesson.edit_url ? /* @__PURE__ */ React.createElement("a", { href: lesson.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Edit") : null, /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => detachLesson(lesson), + className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-3 py-1.5 text-xs font-semibold text-rose-100" + }, + "Remove" + )) + ))), + /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Add from lesson library"), /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass absolute left-4 top-1/2 -translate-y-1/2 text-xs text-slate-500" }), /* @__PURE__ */ React.createElement( + "input", + { + type: "search", + value: lessonSearch, + onChange: (e) => setLessonSearch(e.target.value), + placeholder: "Search lessons by title or category…", + className: "w-full rounded-2xl border border-white/10 bg-black/20 py-2.5 pl-9 pr-4 text-sm text-white outline-none placeholder:text-slate-600" + } + )), filteredAvailableLessons.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.03] px-5 py-4 text-sm text-slate-500" }, lessonSearch.trim() ? "No unattached lessons match your search." : "All lessons are already attached to this course.") : /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, filteredAvailableLessons.map((lesson) => /* @__PURE__ */ React.createElement("div", { key: lesson.id, className: "flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, lesson.difficulty ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, lesson.difficulty) : null, lesson.category ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, lesson.category) : null, !lesson.active ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-amber-200" }, "Inactive") : null), /* @__PURE__ */ React.createElement("p", { className: "mt-1.5 truncate text-sm font-semibold text-white" }, lesson.title)), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => attachLesson(lesson), + className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-3 py-1.5 text-xs font-semibold text-sky-100" + }, + "Add to course" + ))))) + ), /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-publishing", eyebrow: "Release controls", title: "Access, status, and placement", description: "Choose how the course appears in Academy discovery surfaces and when it goes live.", className: sectionClassName("course-publishing") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Access", value: form.data.access_level || "", onChange: (nextValue) => form.setData("access_level", String(nextValue || "")), options: accessField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.access_level })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Difficulty", value: form.data.difficulty || "", onChange: (nextValue) => form.setData("difficulty", String(nextValue || "")), options: difficultyField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.difficulty })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Status", value: form.data.status || "", onChange: (nextValue) => form.setData("status", String(nextValue || "")), options: statusField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.status }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( + TextField$3, + { + label: "Order number", + value: form.data.order_num ?? "", + onChange: (event) => form.setData("order_num", event.target.value), + error: form.errors.order_num, + type: "number", + min: "0", + placeholder: "10", + hint: "Lower numbers float higher in featured and published course lists." + } + ), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Publish at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.data.published_at || "", onChange: (nextValue) => form.setData("published_at", nextValue || ""), clearable: true, className: "bg-black/20" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "If the status is set to published and this is empty, the backend will timestamp it automatically."), /* @__PURE__ */ React.createElement(FieldError$3, { message: form.errors.published_at }))), /* @__PURE__ */ React.createElement( + CheckboxCardField, + { + label: "Feature on newsroom surfaces", + checked: Boolean(form.data.is_featured), + onChange: (event) => form.setData("is_featured", event.target.checked), + description: "Use the featured treatment on Academy homepage rails and the course index. Keep this for courses with strong cover art and a finished outline.", + error: form.errors.is_featured + } + )), /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-seo", eyebrow: "Search surfaces", title: "SEO and OpenGraph", description: "Keep the course crawlable and shareable without overstuffing the main title.", className: sectionClassName("course-seo") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$3, { label: "SEO title", value: form.data.seo_title, onChange: (event) => form.setData("seo_title", event.target.value), error: form.errors.seo_title, maxLength: 180, placeholder: "Optional search title" }), /* @__PURE__ */ React.createElement(TextField$3, { label: "OpenGraph title", value: form.data.og_title, onChange: (event) => form.setData("og_title", event.target.value), error: form.errors.og_title, maxLength: 180, placeholder: "Optional social title" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextAreaField$2, { label: "SEO description", value: form.data.seo_description, onChange: (event) => form.setData("seo_description", event.target.value), error: form.errors.seo_description, rows: 4, hint: "Keep this short and aligned with the course promise." }), /* @__PURE__ */ React.createElement(TextAreaField$2, { label: "OpenGraph description", value: form.data.og_description, onChange: (event) => form.setData("og_description", event.target.value), error: form.errors.og_description, rows: 4, hint: "Used when the course page is shared into external platforms." })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextAreaField$2, { label: "Meta keywords", value: form.data.meta_keywords, onChange: (event) => form.setData("meta_keywords", event.target.value), error: form.errors.meta_keywords, rows: 3, hint: "Comma-separated terms. Keep this focused and editorial, not spammy." }), /* @__PURE__ */ React.createElement(TextField$3, { label: "OpenGraph image", value: form.data.og_image, onChange: (event) => form.setData("og_image", event.target.value), error: form.errors.og_image, placeholder: "Leave empty to fall back to the course artwork" }))), /* @__PURE__ */ React.createElement(SectionCard$6, { id: "course-preview", eyebrow: "Public preview", title: "Rendered course snapshot", description: "Use this tab to scan the media mix, course promise, and rendered long description without the rest of the form competing for attention.", tone: "feature", className: sectionClassName("course-preview") }, /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-slate-950" }, coverPreviewUrl || teaserPreviewUrl ? /* @__PURE__ */ React.createElement("img", { src: coverPreviewUrl || teaserPreviewUrl, alt: "Course hero preview", className: "h-64 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-64 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No course artwork selected yet.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, form.data.difficulty || "beginner"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-200" }, form.data.access_level || "free"), form.data.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/15 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Featured") : null), /* @__PURE__ */ React.createElement("h3", { className: "mt-4 text-3xl font-semibold tracking-[-0.05em] text-white" }, form.data.title || "Untitled academy course"), form.data.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold uppercase tracking-[0.18em] text-amber-100" }, form.data.subtitle) : null, /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, form.data.excerpt || "Add a short course summary to explain what this path helps creators accomplish.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-black/20 p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Description preview"), String(deferredDescription || "").trim() ? /* @__PURE__ */ React.createElement("div", { className: "prose prose-invert mt-4 max-w-none prose-headings:tracking-[-0.03em] prose-p:text-slate-300 prose-li:text-slate-300", dangerouslySetInnerHTML: { __html: deferredDescription } }) : /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-400" }, "The long description is still empty."))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6 xl:sticky xl:top-6 xl:self-start" }, /* @__PURE__ */ React.createElement(SectionCard$6, { eyebrow: "At a glance", title: "Course summary", description: "A compact view of the public URL, media readiness, and the metadata editors see most often." }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/18 bg-sky-300/8 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100/80" }, "Public path"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 break-all text-sm font-semibold text-white" }, coursePathPreview), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, "Use a concise slug so the course URL stays readable in search results and internal links.")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Cover"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, coverPreviewUrl ? "Ready" : "Missing")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Teaser"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, teaserPreviewUrl ? "Ready" : "Missing")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Status"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, form.data.status || "draft")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Duration"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, form.data.estimated_minutes ? `${form.data.estimated_minutes} min` : "Flexible")))), outlineSummary ? /* @__PURE__ */ React.createElement(SectionCard$6, { eyebrow: "Builder pulse", title: "Course outline", description: "A quick summary of what the course builder currently contains so editors do not need to leave this form just to check structure." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Sections"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, outlineSummary.section_count)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Visible sections"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, outlineSummary.visible_section_count)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Attached lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, outlineSummary.lesson_count)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Required lessons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, outlineSummary.required_lesson_count))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/18 bg-sky-300/8 px-4 py-4 text-sm leading-7 text-slate-300" }, outlineSummary.unsectioned_lesson_count > 0 ? `${outlineSummary.unsectioned_lesson_count} lesson${outlineSummary.unsectioned_lesson_count === 1 ? "" : "s"} still sit outside sections. Use the builder if you want the outline to read like a guided chapter path.` : "All attached lessons are currently grouped into sections."), outlineSummary.sections?.length ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, outlineSummary.sections.map((section) => /* @__PURE__ */ React.createElement(OutlineSectionPill, { key: section.id, section }))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.03] px-4 py-5 text-sm text-slate-400" }, "No sections yet. The builder will still allow unsectioned lessons, but adding chapters usually makes the public course easier to scan.")) : null, /* @__PURE__ */ React.createElement(SectionCard$6, { eyebrow: "Metadata pulse", title: "Search and share", description: "A quick scan of the metadata that most often gets missed before publish." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "SEO title"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white" }, form.data.seo_title || "Uses course title by default")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Keywords"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap gap-2" }, metaKeywordItems.length ? metaKeywordItems.map((item) => /* @__PURE__ */ React.createElement("span", { key: item, className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold text-slate-200" }, item)) : /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, "No meta keywords yet."))))))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save course"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: deleteCourse, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null))); +} +const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: CourseEditor +}, Symbol.toStringTag, { value: "Module" })); +function z() { + return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null }; +} +var T = z(); +function G(l3) { + T = l3; +} +var _ = { exec: () => null }; +function d$1(l3, e = "") { + let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s2, r) => { + let i = typeof r == "string" ? r : r.source; + return i = i.replace(m.caret, "$1"), t = t.replace(s2, i), n; + }, getRegex: () => new RegExp(t, e) }; + return n; +} +var Re = ((l3 = "") => { + try { + return !!new RegExp("(?<=1)(?/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`), hrRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), fencesBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}(?:\`\`\`|~~~)`), headingBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}#`), htmlBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}<(?:[a-z].*>|!--)`, "i"), blockquoteBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}>`) }, Te = /^(?:[ \t]*(?:\n|$))+/, Oe = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/, we = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/, I = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/, ye = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, Q = / {0,3}(?:[*+-]|\d{1,9}[.)])/, ie = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/, oe = d$1(ie).replace(/bull/g, Q).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex(), Pe = d$1(ie).replace(/bull/g, Q).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(), j = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/, Se = /^[^\n]+/, F2 = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/, $e = d$1(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", F2).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(), Le = d$1(/^(bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, Q).getRegex(), v = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul", U = /|$))/, _e = d$1("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", U).replace("tag", v).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(), ae = d$1(j).replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(), Me = d$1(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", ae).getRegex(), K = { blockquote: Me, code: Oe, def: $e, fences: we, heading: ye, hr: I, html: _e, lheading: oe, list: Le, newline: Te, paragraph: ae, table: _, text: Se }, re$1 = d$1("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(), ze = { ...K, lheading: Pe, table: re$1, paragraph: d$1(j).replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", re$1).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex() }, Ee = { ...K, html: d$1(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", U).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: _, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d$1(j).replace("hr", I).replace("heading", ` *#{1,6} *[^ +]`).replace("lheading", oe).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() }, Ae = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, Ce = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, le = /^( {2,}|\\)\n(?!\s*$)/, Ie = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Re ? "(?`+)[^`]+\k(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex(), pe = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/, He = d$1(pe, "u").replace(/punct/g, E).getRegex(), Ze = d$1(pe, "u").replace(/punct/g, ue).getRegex(), ce = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)", Ge = d$1(ce, "gu").replace(/notPunctSpace/g, W).replace(/punctSpace/g, H).replace(/punct/g, E).getRegex(), Ne = d$1(ce, "gu").replace(/notPunctSpace/g, qe).replace(/punctSpace/g, De).replace(/punct/g, ue).getRegex(), Qe = d$1("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, W).replace(/punctSpace/g, H).replace(/punct/g, E).getRegex(), je = d$1(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, E).getRegex(), Fe = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)", Ue = d$1(Fe, "gu").replace(/notPunctSpace/g, W).replace(/punctSpace/g, H).replace(/punct/g, E).getRegex(), Ke = d$1(/\\(punct)/, "gu").replace(/punct/g, E).getRegex(), We = d$1(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(), Xe = d$1(U).replace("(?:-->|$)", "-->").getRegex(), Je = d$1("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment", Xe).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(), q = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/, Ve = d$1(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", q).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(), he = d$1(/^!?\[(label)\]\[(ref)\]/).replace("label", q).replace("ref", F2).getRegex(), ke = d$1(/^!?\[(ref)\](?:\[\])?/).replace("ref", F2).getRegex(), Ye = d$1("reflink|nolink(?!\\()", "g").replace("reflink", he).replace("nolink", ke).getRegex(), se = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/, X = { _backpedal: _, anyPunctuation: Ke, autolink: We, blockSkip: ve, br: le, code: Ce, del: _, delLDelim: _, delRDelim: _, emStrongLDelim: He, emStrongRDelimAst: Ge, emStrongRDelimUnd: Qe, escape: Ae, link: Ve, nolink: ke, punctuation: Be, reflink: he, reflinkSearch: Ye, tag: Je, text: Ie, url: _ }, et = { ...X, link: d$1(/^!?\[(label)\]\((.*?)\)/).replace("label", q).getRegex(), reflink: d$1(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", q).getRegex() }, N = { ...X, emStrongRDelimAst: Ne, emStrongLDelim: Ze, delLDelim: je, delRDelim: Ue, url: d$1(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", se).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: d$1(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\": ">", '"': """, "'": "'" }, de = (l3) => nt[l3]; +function O(l3, e) { + if (e) { + if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, de); + } else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, de); + return l3; +} +function J(l3) { + try { + l3 = encodeURI(l3).replace(m.percentDecode, "%"); + } catch { + return null; + } + return l3; +} +function V(l3, e) { + let t = l3.replace(m.findPipe, (r, i, o) => { + let u2 = false, a = i; + for (; --a >= 0 && o[a] === "\\"; ) u2 = !u2; + return u2 ? "|" : " |"; + }), n = t.split(m.splitPipe), s2 = 0; + if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e); + else for (; n.length < e; ) n.push(""); + for (; s2 < n.length; s2++) n[s2] = n[s2].trim().replace(m.slashPipe, "|"); + return n; +} +function $(l3, e, t) { + let n = l3.length; + if (n === 0) return ""; + let s2 = 0; + for (; s2 < n; ) { + let r = l3.charAt(n - s2 - 1); + if (r === e && true) s2++; + else break; + } + return l3.slice(0, n - s2); +} +function Y(l3) { + let e = l3.split(` +`), t = e.length - 1; + for (; t >= 0 && m.blankLine.test(e[t]); ) t--; + return e.length - t <= 2 ? l3 : e.slice(0, t + 1).join(` +`); +} +function ge(l3, e) { + if (l3.indexOf(e[1]) === -1) return -1; + let t = 0; + for (let n = 0; n < l3.length; n++) if (l3[n] === "\\") n++; + else if (l3[n] === e[0]) t++; + else if (l3[n] === e[1] && (t--, t < 0)) return n; + return t > 0 ? -2 : -1; +} +function fe(l3, e = 0) { + let t = e, n = ""; + for (let s2 of l3) if (s2 === " ") { + let r = 4 - t % 4; + n += " ".repeat(r), t += r; + } else n += s2, t++; + return n; +} +function me(l3, e, t, n, s2) { + let r = e.href, i = e.title || null, o = l3[1].replace(s2.other.outputLinkReplace, "$1"); + n.state.inLink = true; + let u2 = { type: l3[0].charAt(0) === "!" ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: n.inlineTokens(o) }; + return n.state.inLink = false, u2; +} +function rt(l3, e, t) { + let n = l3.match(t.other.indentCodeCompensation); + if (n === null) return e; + let s2 = n[1]; + return e.split(` +`).map((r) => { + let i = r.match(t.other.beginningSpace); + if (i === null) return r; + let [o] = i; + return o.length >= s2.length ? r.slice(s2.length) : r; + }).join(` +`); +} +var w = class { + options; + rules; + lexer; + constructor(e) { + this.options = e || T; + } + space(e) { + let t = this.rules.block.newline.exec(e); + if (t && t[0].length > 0) return { type: "space", raw: t[0] }; + } + code(e) { + let t = this.rules.block.code.exec(e); + if (t) { + let n = this.options.pedantic ? t[0] : Y(t[0]), s2 = n.replace(this.rules.other.codeRemoveIndent, ""); + return { type: "code", raw: n, codeBlockStyle: "indented", text: s2 }; + } + } + fences(e) { + let t = this.rules.block.fences.exec(e); + if (t) { + let n = t[0], s2 = rt(n, t[3] || "", this.rules); + return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s2 }; + } + } + heading(e) { + let t = this.rules.block.heading.exec(e); + if (t) { + let n = t[2].trim(); + if (this.rules.other.endingHash.test(n)) { + let s2 = $(n, "#"); + (this.options.pedantic || !s2 || this.rules.other.endingSpaceChar.test(s2)) && (n = s2.trim()); + } + return { type: "heading", raw: $(t[0], ` +`), depth: t[1].length, text: n, tokens: this.lexer.inline(n) }; + } + } + hr(e) { + let t = this.rules.block.hr.exec(e); + if (t) return { type: "hr", raw: $(t[0], ` +`) }; + } + blockquote(e) { + let t = this.rules.block.blockquote.exec(e); + if (t) { + let n = $(t[0], ` +`).split(` +`), s2 = "", r = "", i = []; + for (; n.length > 0; ) { + let o = false, u2 = [], a; + for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u2.push(n[a]), o = true; + else if (!o) u2.push(n[a]); + else break; + n = n.slice(a); + let c = u2.join(` +`), p = c.replace(this.rules.other.blockquoteSetextReplace, ` + $1`).replace(this.rules.other.blockquoteSetextReplace2, ""); + s2 = s2 ? `${s2} +${c}` : c, r = r ? `${r} +${p}` : p; + let k2 = this.lexer.state.top; + if (this.lexer.state.top = true, this.lexer.blockTokens(p, i, true), this.lexer.state.top = k2, n.length === 0) break; + let h = i.at(-1); + if (h?.type === "code") break; + if (h?.type === "blockquote") { + let R = h, f2 = R.raw + ` +` + n.join(` +`), S = this.blockquote(f2); + i[i.length - 1] = S, s2 = s2.substring(0, s2.length - R.raw.length) + S.raw, r = r.substring(0, r.length - R.text.length) + S.text; + break; + } else if (h?.type === "list") { + let R = h, f2 = R.raw + ` +` + n.join(` +`), S = this.list(f2); + i[i.length - 1] = S, s2 = s2.substring(0, s2.length - h.raw.length) + S.raw, r = r.substring(0, r.length - R.raw.length) + S.raw, n = f2.substring(i.at(-1).raw.length).split(` +`); + continue; + } + } + return { type: "blockquote", raw: s2, tokens: i, text: r }; + } + } + list(e) { + let t = this.rules.block.list.exec(e); + if (t) { + let n = t[1].trim(), s2 = n.length > 1, r = { type: "list", raw: "", ordered: s2, start: s2 ? +n.slice(0, -1) : "", loose: false, items: [] }; + n = s2 ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s2 ? n : "[*+-]"); + let i = this.rules.other.listItemRegex(n), o = false; + for (; e; ) { + let a = false, c = "", p = ""; + if (!(t = i.exec(e)) || this.rules.block.hr.test(e)) break; + c = t[0], e = e.substring(c.length); + let k2 = fe(t[2].split(` +`, 1)[0], t[1].length), h = e.split(` +`, 1)[0], R = !k2.trim(), f2 = 0; + if (this.options.pedantic ? (f2 = 2, p = k2.trimStart()) : R ? f2 = t[1].length + 1 : (f2 = k2.search(this.rules.other.nonSpaceChar), f2 = f2 > 4 ? 1 : f2, p = k2.slice(f2), f2 += t[1].length), R && this.rules.other.blankLine.test(h) && (c += h + ` +`, e = e.substring(h.length + 1), a = true), !a) { + let S = this.rules.other.nextBulletRegex(f2), ee2 = this.rules.other.hrRegex(f2), te2 = this.rules.other.fencesBeginRegex(f2), ne = this.rules.other.headingBeginRegex(f2), xe2 = this.rules.other.htmlBeginRegex(f2), be = this.rules.other.blockquoteBeginRegex(f2); + for (; e; ) { + let Z2 = e.split(` +`, 1)[0], C2; + if (h = Z2, this.options.pedantic ? (h = h.replace(this.rules.other.listReplaceNesting, " "), C2 = h) : C2 = h.replace(this.rules.other.tabCharGlobal, " "), te2.test(h) || ne.test(h) || xe2.test(h) || be.test(h) || S.test(h) || ee2.test(h)) break; + if (C2.search(this.rules.other.nonSpaceChar) >= f2 || !h.trim()) p += ` +` + C2.slice(f2); + else { + if (R || k2.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || te2.test(k2) || ne.test(k2) || ee2.test(k2)) break; + p += ` +` + h; + } + R = !h.trim(), c += Z2 + ` +`, e = e.substring(Z2.length + 1), k2 = C2.slice(f2); + } + } + r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(c) && (o = true)), r.items.push({ type: "list_item", raw: c, task: !!this.options.gfm && this.rules.other.listIsTask.test(p), loose: false, text: p, tokens: [] }), r.raw += c; + } + let u2 = r.items.at(-1); + if (u2) u2.raw = u2.raw.trimEnd(), u2.text = u2.text.trimEnd(); + else return; + r.raw = r.raw.trimEnd(); + for (let a of r.items) { + this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []); + let c = a.tokens[0]; + if (a.task && (c?.type === "text" || c?.type === "paragraph")) { + a.text = a.text.replace(this.rules.other.listReplaceTask, ""), c.raw = c.raw.replace(this.rules.other.listReplaceTask, ""), c.text = c.text.replace(this.rules.other.listReplaceTask, ""); + for (let k2 = this.lexer.inlineQueue.length - 1; k2 >= 0; k2--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[k2].src)) { + this.lexer.inlineQueue[k2].src = this.lexer.inlineQueue[k2].src.replace(this.rules.other.listReplaceTask, ""); + break; + } + let p = this.rules.other.listTaskCheckbox.exec(a.raw); + if (p) { + let k2 = { type: "checkbox", raw: p[0] + " ", checked: p[0] !== "[ ]" }; + a.checked = k2.checked, r.loose ? a.tokens[0] && ["paragraph", "text"].includes(a.tokens[0].type) && "tokens" in a.tokens[0] && a.tokens[0].tokens ? (a.tokens[0].raw = k2.raw + a.tokens[0].raw, a.tokens[0].text = k2.raw + a.tokens[0].text, a.tokens[0].tokens.unshift(k2)) : a.tokens.unshift({ type: "paragraph", raw: k2.raw, text: k2.raw, tokens: [k2] }) : a.tokens.unshift(k2); + } + } else a.task && (a.task = false); + if (!r.loose) { + let p = a.tokens.filter((h) => h.type === "space"), k2 = p.length > 0 && p.some((h) => this.rules.other.anyLine.test(h.raw)); + r.loose = k2; + } + } + if (r.loose) for (let a of r.items) { + a.loose = true; + for (let c of a.tokens) c.type === "text" && (c.type = "paragraph"); + } + return r; + } + } + html(e) { + let t = this.rules.block.html.exec(e); + if (t) { + let n = Y(t[0]); + return { type: "html", block: true, raw: n, pre: t[1] === "pre" || t[1] === "script" || t[1] === "style", text: n }; + } + } + def(e) { + let t = this.rules.block.def.exec(e); + if (t) { + let n = t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, " "), s2 = t[2] ? t[2].replace(this.rules.other.hrefBrackets, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "", r = t[3] ? t[3].substring(1, t[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : t[3]; + return { type: "def", tag: n, raw: $(t[0], ` +`), href: s2, title: r }; + } + } + table(e) { + let t = this.rules.block.table.exec(e); + if (!t || !this.rules.other.tableDelimiter.test(t[2])) return; + let n = V(t[1]), s2 = t[2].replace(this.rules.other.tableAlignChars, "").split("|"), r = t[3]?.trim() ? t[3].replace(this.rules.other.tableRowBlankLine, "").split(` +`) : [], i = { type: "table", raw: $(t[0], ` +`), header: [], align: [], rows: [] }; + if (n.length === s2.length) { + for (let o of s2) this.rules.other.tableAlignRight.test(o) ? i.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? i.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? i.align.push("left") : i.align.push(null); + for (let o = 0; o < n.length; o++) i.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: i.align[o] }); + for (let o of r) i.rows.push(V(o, i.header.length).map((u2, a) => ({ text: u2, tokens: this.lexer.inline(u2), header: false, align: i.align[a] }))); + return i; + } + } + lheading(e) { + let t = this.rules.block.lheading.exec(e); + if (t) { + let n = t[1].trim(); + return { type: "heading", raw: $(t[0], ` +`), depth: t[2].charAt(0) === "=" ? 1 : 2, text: n, tokens: this.lexer.inline(n) }; + } + } + paragraph(e) { + let t = this.rules.block.paragraph.exec(e); + if (t) { + let n = t[1].charAt(t[1].length - 1) === ` +` ? t[1].slice(0, -1) : t[1]; + return { type: "paragraph", raw: t[0], text: n, tokens: this.lexer.inline(n) }; + } + } + text(e) { + let t = this.rules.block.text.exec(e); + if (t) return { type: "text", raw: t[0], text: t[0], tokens: this.lexer.inline(t[0]) }; + } + escape(e) { + let t = this.rules.inline.escape.exec(e); + if (t) return { type: "escape", raw: t[0], text: t[1] }; + } + tag(e) { + let t = this.rules.inline.tag.exec(e); + if (t) return !this.lexer.state.inLink && this.rules.other.startATag.test(t[0]) ? this.lexer.state.inLink = true : this.lexer.state.inLink && this.rules.other.endATag.test(t[0]) && (this.lexer.state.inLink = false), !this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(t[0]) ? this.lexer.state.inRawBlock = true : this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(t[0]) && (this.lexer.state.inRawBlock = false), { type: "html", raw: t[0], inLink: this.lexer.state.inLink, inRawBlock: this.lexer.state.inRawBlock, block: false, text: t[0] }; + } + link(e) { + let t = this.rules.inline.link.exec(e); + if (t) { + let n = t[2].trim(); + if (!this.options.pedantic && this.rules.other.startAngleBracket.test(n)) { + if (!this.rules.other.endAngleBracket.test(n)) return; + let i = $(n.slice(0, -1), "\\"); + if ((n.length - i.length) % 2 === 0) return; + } else { + let i = ge(t[2], "()"); + if (i === -2) return; + if (i > -1) { + let u2 = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i; + t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, u2).trim(), t[3] = ""; + } + } + let s2 = t[2], r = ""; + if (this.options.pedantic) { + let i = this.rules.other.pedanticHrefTitle.exec(s2); + i && (s2 = i[1], r = i[3]); + } else r = t[3] ? t[3].slice(1, -1) : ""; + return s2 = s2.trim(), this.rules.other.startAngleBracket.test(s2) && (this.options.pedantic && !this.rules.other.endAngleBracket.test(n) ? s2 = s2.slice(1) : s2 = s2.slice(1, -1)), me(t, { href: s2 && s2.replace(this.rules.inline.anyPunctuation, "$1"), title: r && r.replace(this.rules.inline.anyPunctuation, "$1") }, t[0], this.lexer, this.rules); + } + } + reflink(e, t) { + let n; + if ((n = this.rules.inline.reflink.exec(e)) || (n = this.rules.inline.nolink.exec(e))) { + let s2 = (n[2] || n[1]).replace(this.rules.other.multipleSpaceGlobal, " "), r = t[s2.toLowerCase()]; + if (!r) { + let i = n[0].charAt(0); + return { type: "text", raw: i, text: i }; + } + return me(n, r, n[0], this.lexer, this.rules); + } + } + emStrong(e, t, n = "") { + let s2 = this.rules.inline.emStrongLDelim.exec(e); + if (!s2 || !s2[1] && !s2[2] && !s2[3] && !s2[4] || s2[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return; + if (!(s2[1] || s2[3] || "") || !n || this.rules.inline.punctuation.exec(n)) { + let i = [...s2[0]].length - 1, o, u2, a = i, c = 0, p = s2[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; + for (p.lastIndex = 0, t = t.slice(-1 * e.length + i); (s2 = p.exec(t)) !== null; ) { + if (o = s2[1] || s2[2] || s2[3] || s2[4] || s2[5] || s2[6], !o) continue; + if (u2 = [...o].length, s2[3] || s2[4]) { + a += u2; + continue; + } else if ((s2[5] || s2[6]) && i % 3 && !((i + u2) % 3)) { + c += u2; + continue; + } + if (a -= u2, a > 0) continue; + u2 = Math.min(u2, u2 + a + c); + let k2 = [...s2[0]][0].length, h = e.slice(0, i + s2.index + k2 + u2); + if (Math.min(i, u2) % 2) { + let f2 = h.slice(1, -1); + return { type: "em", raw: h, text: f2, tokens: this.lexer.inlineTokens(f2) }; + } + let R = h.slice(2, -2); + return { type: "strong", raw: h, text: R, tokens: this.lexer.inlineTokens(R) }; + } + } + } + codespan(e) { + let t = this.rules.inline.code.exec(e); + if (t) { + let n = t[2].replace(this.rules.other.newLineCharGlobal, " "), s2 = this.rules.other.nonSpaceChar.test(n), r = this.rules.other.startingSpaceChar.test(n) && this.rules.other.endingSpaceChar.test(n); + return s2 && r && (n = n.substring(1, n.length - 1)), { type: "codespan", raw: t[0], text: n }; + } + } + br(e) { + let t = this.rules.inline.br.exec(e); + if (t) return { type: "br", raw: t[0] }; + } + del(e, t, n = "") { + let s2 = this.rules.inline.delLDelim.exec(e); + if (!s2) return; + if (!(s2[1] || "") || !n || this.rules.inline.punctuation.exec(n)) { + let i = [...s2[0]].length - 1, o, u2, a = i, c = this.rules.inline.delRDelim; + for (c.lastIndex = 0, t = t.slice(-1 * e.length + i); (s2 = c.exec(t)) !== null; ) { + if (o = s2[1] || s2[2] || s2[3] || s2[4] || s2[5] || s2[6], !o || (u2 = [...o].length, u2 !== i)) continue; + if (s2[3] || s2[4]) { + a += u2; + continue; + } + if (a -= u2, a > 0) continue; + u2 = Math.min(u2, u2 + a); + let p = [...s2[0]][0].length, k2 = e.slice(0, i + s2.index + p + u2), h = k2.slice(i, -i); + return { type: "del", raw: k2, text: h, tokens: this.lexer.inlineTokens(h) }; + } + } + } + autolink(e) { + let t = this.rules.inline.autolink.exec(e); + if (t) { + let n, s2; + return t[2] === "@" ? (n = t[1], s2 = "mailto:" + n) : (n = t[1], s2 = n), { type: "link", raw: t[0], text: n, href: s2, tokens: [{ type: "text", raw: n, text: n }] }; + } + } + url(e) { + let t; + if (t = this.rules.inline.url.exec(e)) { + let n, s2; + if (t[2] === "@") n = t[0], s2 = "mailto:" + n; + else { + let r; + do + r = t[0], t[0] = this.rules.inline._backpedal.exec(t[0])?.[0] ?? ""; + while (r !== t[0]); + n = t[0], t[1] === "www." ? s2 = "http://" + t[0] : s2 = t[0]; + } + return { type: "link", raw: t[0], text: n, href: s2, tokens: [{ type: "text", raw: n, text: n }] }; + } + } + inlineText(e) { + let t = this.rules.inline.text.exec(e); + if (t) { + let n = this.lexer.state.inRawBlock; + return { type: "text", raw: t[0], text: t[0], escaped: n }; + } + } +}; +var x = class l { + tokens; + options; + state; + inlineQueue; + tokenizer; + constructor(e) { + this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || T, this.options.tokenizer = this.options.tokenizer || new w(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, top: true }; + let t = { other: m, block: B.normal, inline: A.normal }; + this.options.pedantic ? (t.block = B.pedantic, t.inline = A.pedantic) : this.options.gfm && (t.block = B.gfm, this.options.breaks ? t.inline = A.breaks : t.inline = A.gfm), this.tokenizer.rules = t; + } + static get rules() { + return { block: B, inline: A }; + } + static lex(e, t) { + return new l(t).lex(e); + } + static lexInline(e, t) { + return new l(t).inlineTokens(e); + } + lex(e) { + e = e.replace(m.carriageReturn, ` +`), this.blockTokens(e, this.tokens); + for (let t = 0; t < this.inlineQueue.length; t++) { + let n = this.inlineQueue[t]; + this.inlineTokens(n.src, n.tokens); + } + return this.inlineQueue = [], this.tokens; + } + blockTokens(e, t = [], n = false) { + this.tokenizer.lexer = this, this.options.pedantic && (e = e.replace(m.tabCharGlobal, " ").replace(m.spaceLine, "")); + let s2 = 1 / 0; + for (; e; ) { + if (e.length < s2) s2 = e.length; + else { + this.infiniteLoopError(e.charCodeAt(0)); + break; + } + let r; + if (this.options.extensions?.block?.some((o) => (r = o.call({ lexer: this }, e, t)) ? (e = e.substring(r.raw.length), t.push(r), true) : false)) continue; + if (r = this.tokenizer.space(e)) { + e = e.substring(r.raw.length); + let o = t.at(-1); + r.raw.length === 1 && o !== void 0 ? o.raw += ` +` : t.push(r); + continue; + } + if (r = this.tokenizer.code(e)) { + e = e.substring(r.raw.length); + let o = t.at(-1); + o?.type === "paragraph" || o?.type === "text" ? (o.raw += (o.raw.endsWith(` +`) ? "" : ` +`) + r.raw, o.text += ` +` + r.text, this.inlineQueue.at(-1).src = o.text) : t.push(r); + continue; + } + if (r = this.tokenizer.fences(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.heading(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.hr(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.blockquote(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.list(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.html(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.def(e)) { + e = e.substring(r.raw.length); + let o = t.at(-1); + o?.type === "paragraph" || o?.type === "text" ? (o.raw += (o.raw.endsWith(` +`) ? "" : ` +`) + r.raw, o.text += ` +` + r.raw, this.inlineQueue.at(-1).src = o.text) : this.tokens.links[r.tag] || (this.tokens.links[r.tag] = { href: r.href, title: r.title }, t.push(r)); + continue; + } + if (r = this.tokenizer.table(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + if (r = this.tokenizer.lheading(e)) { + e = e.substring(r.raw.length), t.push(r); + continue; + } + let i = e; + if (this.options.extensions?.startBlock) { + let o = 1 / 0, u2 = e.slice(1), a; + this.options.extensions.startBlock.forEach((c) => { + a = c.call({ lexer: this }, u2), typeof a == "number" && a >= 0 && (o = Math.min(o, a)); + }), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1)); + } + if (this.state.top && (r = this.tokenizer.paragraph(i))) { + let o = t.at(-1); + n && o?.type === "paragraph" ? (o.raw += (o.raw.endsWith(` +`) ? "" : ` +`) + r.raw, o.text += ` +` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = o.text) : t.push(r), n = i.length !== e.length, e = e.substring(r.raw.length); + continue; + } + if (r = this.tokenizer.text(e)) { + e = e.substring(r.raw.length); + let o = t.at(-1); + o?.type === "text" ? (o.raw += (o.raw.endsWith(` +`) ? "" : ` +`) + r.raw, o.text += ` +` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = o.text) : t.push(r); + continue; + } + if (e) { + this.infiniteLoopError(e.charCodeAt(0)); + break; + } + } + return this.state.top = true, t; + } + inline(e, t = []) { + return this.inlineQueue.push({ src: e, tokens: t }), t; + } + inlineTokens(e, t = []) { + this.tokenizer.lexer = this; + let n = e, s2 = null; + if (this.tokens.links) { + let a = Object.keys(this.tokens.links); + if (a.length > 0) for (; (s2 = this.tokenizer.rules.inline.reflinkSearch.exec(n)) !== null; ) a.includes(s2[0].slice(s2[0].lastIndexOf("[") + 1, -1)) && (n = n.slice(0, s2.index) + "[" + "a".repeat(s2[0].length - 2) + "]" + n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex)); + } + for (; (s2 = this.tokenizer.rules.inline.anyPunctuation.exec(n)) !== null; ) n = n.slice(0, s2.index) + "++" + n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); + let r; + for (; (s2 = this.tokenizer.rules.inline.blockSkip.exec(n)) !== null; ) r = s2[2] ? s2[2].length : 0, n = n.slice(0, s2.index + r) + "[" + "a".repeat(s2[0].length - r - 2) + "]" + n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); + n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n; + let i = false, o = "", u2 = 1 / 0; + for (; e; ) { + if (e.length < u2) u2 = e.length; + else { + this.infiniteLoopError(e.charCodeAt(0)); + break; + } + i || (o = ""), i = false; + let a; + if (this.options.extensions?.inline?.some((p) => (a = p.call({ lexer: this }, e, t)) ? (e = e.substring(a.raw.length), t.push(a), true) : false)) continue; + if (a = this.tokenizer.escape(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.tag(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.link(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.reflink(e, this.tokens.links)) { + e = e.substring(a.raw.length); + let p = t.at(-1); + a.type === "text" && p?.type === "text" ? (p.raw += a.raw, p.text += a.text) : t.push(a); + continue; + } + if (a = this.tokenizer.emStrong(e, n, o)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.codespan(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.br(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.del(e, n, o)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (a = this.tokenizer.autolink(e)) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + if (!this.state.inLink && (a = this.tokenizer.url(e))) { + e = e.substring(a.raw.length), t.push(a); + continue; + } + let c = e; + if (this.options.extensions?.startInline) { + let p = 1 / 0, k2 = e.slice(1), h; + this.options.extensions.startInline.forEach((R) => { + h = R.call({ lexer: this }, k2), typeof h == "number" && h >= 0 && (p = Math.min(p, h)); + }), p < 1 / 0 && p >= 0 && (c = e.substring(0, p + 1)); + } + if (a = this.tokenizer.inlineText(c)) { + e = e.substring(a.raw.length), a.raw.slice(-1) !== "_" && (o = a.raw.slice(-1)), i = true; + let p = t.at(-1); + p?.type === "text" ? (p.raw += a.raw, p.text += a.text) : t.push(a); + continue; + } + if (e) { + this.infiniteLoopError(e.charCodeAt(0)); + break; + } + } + return t; + } + infiniteLoopError(e) { + let t = "Infinite loop on byte: " + e; + if (this.options.silent) console.error(t); + else throw new Error(t); + } +}; +var y = class { + options; + parser; + constructor(e) { + this.options = e || T; + } + space(e) { + return ""; + } + code({ text: e, lang: t, escaped: n }) { + let s2 = (t || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + ` +`; + return s2 ? '
' + (n ? r : O(r, true)) + `
+` : "
" + (n ? r : O(r, true)) + `
+`; + } + blockquote({ tokens: e }) { + return `
+${this.parser.parse(e)}
+`; + } + html({ text: e }) { + return e; + } + def(e) { + return ""; + } + heading({ tokens: e, depth: t }) { + return `${this.parser.parseInline(e)} +`; + } + hr(e) { + return `
+`; + } + list(e) { + let t = e.ordered, n = e.start, s2 = ""; + for (let o = 0; o < e.items.length; o++) { + let u2 = e.items[o]; + s2 += this.listitem(u2); + } + let r = t ? "ol" : "ul", i = t && n !== 1 ? ' start="' + n + '"' : ""; + return "<" + r + i + `> +` + s2 + " +`; + } + listitem(e) { + return `
  • ${this.parser.parse(e.tokens)}
  • +`; + } + checkbox({ checked: e }) { + return " '; + } + paragraph({ tokens: e }) { + return `

    ${this.parser.parseInline(e)}

    +`; + } + table(e) { + let t = "", n = ""; + for (let r = 0; r < e.header.length; r++) n += this.tablecell(e.header[r]); + t += this.tablerow({ text: n }); + let s2 = ""; + for (let r = 0; r < e.rows.length; r++) { + let i = e.rows[r]; + n = ""; + for (let o = 0; o < i.length; o++) n += this.tablecell(i[o]); + s2 += this.tablerow({ text: n }); + } + return s2 && (s2 = `${s2}`), ` + +` + t + ` +` + s2 + `
    +`; + } + tablerow({ text: e }) { + return ` +${e} +`; + } + tablecell(e) { + let t = this.parser.parseInline(e.tokens), n = e.header ? "th" : "td"; + return (e.align ? `<${n} align="${e.align}">` : `<${n}>`) + t + ` +`; + } + strong({ tokens: e }) { + return `${this.parser.parseInline(e)}`; + } + em({ tokens: e }) { + return `${this.parser.parseInline(e)}`; + } + codespan({ text: e }) { + return `${O(e, true)}`; + } + br(e) { + return "
    "; + } + del({ tokens: e }) { + return `${this.parser.parseInline(e)}`; + } + link({ href: e, title: t, tokens: n }) { + let s2 = this.parser.parseInline(n), r = J(e); + if (r === null) return s2; + e = r; + let i = '
    ", i; + } + image({ href: e, title: t, text: n, tokens: s2 }) { + s2 && (n = this.parser.parseInline(s2, this.parser.textRenderer)); + let r = J(e); + if (r === null) return O(n); + e = r; + let i = `${O(n)} { + let o = r[i].flat(1 / 0); + n = n.concat(this.walkTokens(o, t)); + }) : r.tokens && (n = n.concat(this.walkTokens(r.tokens, t))); + } + } + return n; + } + use(...e) { + let t = this.defaults.extensions || { renderers: {}, childTokens: {} }; + return e.forEach((n) => { + let s2 = { ...n }; + if (s2.async = this.defaults.async || s2.async || false, n.extensions && (n.extensions.forEach((r) => { + if (!r.name) throw new Error("extension name required"); + if ("renderer" in r) { + let i = t.renderers[r.name]; + i ? t.renderers[r.name] = function(...o) { + let u2 = r.renderer.apply(this, o); + return u2 === false && (u2 = i.apply(this, o)), u2; + } : t.renderers[r.name] = r.renderer; + } + if ("tokenizer" in r) { + if (!r.level || r.level !== "block" && r.level !== "inline") throw new Error("extension level must be 'block' or 'inline'"); + let i = t[r.level]; + i ? i.unshift(r.tokenizer) : t[r.level] = [r.tokenizer], r.start && (r.level === "block" ? t.startBlock ? t.startBlock.push(r.start) : t.startBlock = [r.start] : r.level === "inline" && (t.startInline ? t.startInline.push(r.start) : t.startInline = [r.start])); + } + "childTokens" in r && r.childTokens && (t.childTokens[r.name] = r.childTokens); + }), s2.extensions = t), n.renderer) { + let r = this.defaults.renderer || new y(this.defaults); + for (let i in n.renderer) { + if (!(i in r)) throw new Error(`renderer '${i}' does not exist`); + if (["options", "parser"].includes(i)) continue; + let o = i, u2 = n.renderer[o], a = r[o]; + r[o] = (...c) => { + let p = u2.apply(r, c); + return p === false && (p = a.apply(r, c)), p || ""; + }; + } + s2.renderer = r; + } + if (n.tokenizer) { + let r = this.defaults.tokenizer || new w(this.defaults); + for (let i in n.tokenizer) { + if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`); + if (["options", "rules", "lexer"].includes(i)) continue; + let o = i, u2 = n.tokenizer[o], a = r[o]; + r[o] = (...c) => { + let p = u2.apply(r, c); + return p === false && (p = a.apply(r, c)), p; + }; + } + s2.tokenizer = r; + } + if (n.hooks) { + let r = this.defaults.hooks || new P(); + for (let i in n.hooks) { + if (!(i in r)) throw new Error(`hook '${i}' does not exist`); + if (["options", "block"].includes(i)) continue; + let o = i, u2 = n.hooks[o], a = r[o]; + P.passThroughHooks.has(i) ? r[o] = (c) => { + if (this.defaults.async && P.passThroughHooksRespectAsync.has(i)) return (async () => { + let k2 = await u2.call(r, c); + return a.call(r, k2); + })(); + let p = u2.call(r, c); + return a.call(r, p); + } : r[o] = (...c) => { + if (this.defaults.async) return (async () => { + let k2 = await u2.apply(r, c); + return k2 === false && (k2 = await a.apply(r, c)), k2; + })(); + let p = u2.apply(r, c); + return p === false && (p = a.apply(r, c)), p; + }; + } + s2.hooks = r; + } + if (n.walkTokens) { + let r = this.defaults.walkTokens, i = n.walkTokens; + s2.walkTokens = function(o) { + let u2 = []; + return u2.push(i.call(this, o)), r && (u2 = u2.concat(r.call(this, o))), u2; + }; + } + this.defaults = { ...this.defaults, ...s2 }; + }), this; + } + setOptions(e) { + return this.defaults = { ...this.defaults, ...e }, this; + } + lexer(e, t) { + return x.lex(e, t ?? this.defaults); + } + parser(e, t) { + return b.parse(e, t ?? this.defaults); + } + parseMarkdown(e) { + return (n, s2) => { + let r = { ...s2 }, i = { ...this.defaults, ...r }, o = this.onError(!!i.silent, !!i.async); + if (this.defaults.async === true && r.async === false) return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.")); + if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null")); + if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected")); + if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => { + let u2 = i.hooks ? await i.hooks.preprocess(n) : n, c = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(u2, i), p = i.hooks ? await i.hooks.processAllTokens(c) : c; + i.walkTokens && await Promise.all(this.walkTokens(p, i.walkTokens)); + let h = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(p, i); + return i.hooks ? await i.hooks.postprocess(h) : h; + })().catch(o); + try { + i.hooks && (n = i.hooks.preprocess(n)); + let a = (i.hooks ? i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(n, i); + i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens); + let p = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i); + return i.hooks && (p = i.hooks.postprocess(p)), p; + } catch (u2) { + return o(u2); + } + }; + } + onError(e, t) { + return (n) => { + if (n.message += ` +Please report this to https://github.com/markedjs/marked.`, e) { + let s2 = "

    An error occurred:

    " + O(n.message + "", true) + "
    "; + return t ? Promise.resolve(s2) : s2; + } + if (t) return Promise.reject(n); + throw n; + }; + } +}; +var M = new D(); +function g$1(l3, e) { + return M.parse(l3, e); +} +g$1.options = g$1.setOptions = function(l3) { + return M.setOptions(l3), g$1.defaults = M.defaults, G(g$1.defaults), g$1; +}; +g$1.getDefaults = z; +g$1.defaults = T; +g$1.use = function(...l3) { + return M.use(...l3), g$1.defaults = M.defaults, G(g$1.defaults), g$1; +}; +g$1.walkTokens = function(l3, e) { + return M.walkTokens(l3, e); +}; +g$1.parseInline = M.parseInline; +g$1.Parser = b; +g$1.parser = b.parse; +g$1.Renderer = y; +g$1.TextRenderer = L; +g$1.Lexer = x; +g$1.lexer = x.lex; +g$1.Tokenizer = w; +g$1.Hooks = P; +g$1.parse = g$1; +g$1.options; +g$1.setOptions; +g$1.use; +g$1.walkTokens; +g$1.parseInline; +b.parse; +x.lex; +let lessonMarkdownTurndown = null; +let lessonMarkdownTurndownPromise = null; +async function loadLessonMarkdownTurndown() { + if (lessonMarkdownTurndown) { + return lessonMarkdownTurndown; + } + if (typeof window === "undefined") { + return null; + } + if (!lessonMarkdownTurndownPromise) { + lessonMarkdownTurndownPromise = import("./assets/turndown.es-8lfE8z0s.js").then(({ default: TurndownService }) => new TurndownService({ + headingStyle: "atx", + codeBlockStyle: "fenced", + bulletListMarker: "-", + emDelimiter: "*" + })).then((service) => { + lessonMarkdownTurndown = service; + return service; + }).catch(() => null); + } + return lessonMarkdownTurndownPromise; +} function getField$1(fields, name2) { return fields.find((field) => field.name === name2) || null; } -const LESSON_SECTION_NAV_ITEMS = [ - { id: "lesson-story-setup", label: "Story setup" }, - { id: "lesson-body-editor", label: "Main article" }, - { id: "lesson-ai-comparisons", label: "AI comparisons" }, - { id: "lesson-publishing", label: "Publishing" }, - { id: "lesson-seo", label: "SEO" }, - { id: "lesson-categories", label: "Categories" }, - { id: "lesson-cover", label: "Cover image" }, - { id: "lesson-preview", label: "Preview" } +const LESSON_EDITOR_TABS = [ + { + id: "write", + label: "Write", + description: "Headline, summary, and the full lesson article.", + icon: "fa-pen-nib", + sections: ["lesson-story-setup", "lesson-body-editor"] + }, + { + id: "blocks", + label: "Blocks", + description: "Reusable AI comparison modules and structured lesson inserts.", + icon: "fa-layer-group", + sections: ["lesson-ai-comparisons"] + }, + { + id: "courses", + label: "Courses", + description: "Attach this lesson to courses, manage its public numbering, and reorder it inside guided paths.", + icon: "fa-diagram-project", + sections: ["lesson-course-numbering", "lesson-course-manager"] + }, + { + id: "publish", + label: "Publish", + description: "Visibility, discovery settings, scheduling, and search surfaces.", + icon: "fa-rocket-launch", + sections: ["lesson-publishing", "lesson-seo"] + }, + { + id: "assets", + label: "Assets", + description: "Categories, hero media, and article imagery.", + icon: "fa-images", + sections: ["lesson-categories", "lesson-cover", "lesson-article-cover"] + }, + { + id: "revisions", + label: "Revisions", + description: "Review saved lesson snapshots and restore the full lesson or a single field.", + icon: "fa-clock-rotate-left", + sections: ["lesson-revisions"] + }, + { + id: "preview", + label: "Preview", + description: "Preview the lesson card, article imagery, and rendered body.", + icon: "fa-eye", + sections: ["lesson-preview"] + } +]; +const LESSON_FIELD_TAB_MAP = { + title: "write", + slug: "write", + excerpt: "write", + content: "write", + content_markdown: "write", + lesson_type: "publish", + difficulty: "publish", + access_level: "publish", + reading_minutes: "publish", + tags: "publish", + series_name: "publish", + lesson_number: "courses", + course_order: "courses", + course_ids: "courses", + category_id: "assets", + published_at: "publish", + featured: "publish", + active: "publish", + seo_title: "publish", + seo_description: "publish", + video_url: "publish", + cover_image: "assets", + article_cover_image: "assets" +}; +const LESSON_REVISION_FIELD_OPTIONS = [ + { value: "title", label: "Title" }, + { value: "slug", label: "Slug" }, + { value: "lesson_number", label: "Lesson number" }, + { value: "course_order", label: "Course order" }, + { value: "series_name", label: "Series name" }, + { value: "excerpt", label: "Excerpt" }, + { value: "content", label: "Article body" }, + { value: "difficulty", label: "Difficulty" }, + { value: "access_level", label: "Access level" }, + { value: "lesson_type", label: "Lesson type" }, + { value: "cover_image", label: "Cover image" }, + { value: "article_cover_image", label: "Article cover image" }, + { value: "tags", label: "Microtags" }, + { value: "video_url", label: "Video URL" }, + { value: "reading_minutes", label: "Reading minutes" }, + { value: "featured", label: "Featured toggle" }, + { value: "active", label: "Active toggle" }, + { value: "published_at", label: "Publish date" }, + { value: "seo_title", label: "SEO title" }, + { value: "seo_description", label: "SEO description" }, + { value: "course_ids", label: "Course attachments" }, + { value: "blocks", label: "AI comparison blocks" } ]; let comparisonEditorSequence = 0; function nextComparisonEditorKey(prefix) { @@ -16202,20 +18899,50 @@ function FieldError$2({ message }) { if (!message) return null; return /* @__PURE__ */ React.createElement("p", { className: "text-xs text-rose-300" }, message); } -function SectionCard$5({ id, eyebrow, title, description, actions, children, tone = "default" }) { +function SectionCard$5({ id, eyebrow, title, description, actions, children, tone = "default", className = "", contentClassName = "" }) { const toneClass = tone === "feature" ? "bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.92))] shadow-[0_24px_70px_rgba(2,6,23,0.28)]" : "bg-white/[0.03]"; - return /* @__PURE__ */ React.createElement("section", { id, className: `min-w-0 scroll-mt-24 rounded-[28px] border border-white/10 p-5 ${toneClass}` }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, eyebrow ? /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/75" }, eyebrow) : null, /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold tracking-[-0.03em] text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, description) : null), actions ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, actions) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5" }, children)); + return /* @__PURE__ */ React.createElement("section", { id, className: `min-w-0 scroll-mt-24 rounded-[28px] border border-white/10 p-5 ${toneClass} ${className}`.trim() }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, eyebrow ? /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/75" }, eyebrow) : null, /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold tracking-[-0.03em] text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, description) : null), actions ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, actions) : null), /* @__PURE__ */ React.createElement("div", { className: `mt-5 ${contentClassName}`.trim() }, children)); } -function SectionNav({ items }) { - return /* @__PURE__ */ React.createElement("div", { className: "sticky top-4 z-20 rounded-[24px] border border-white/10 bg-[linear-gradient(180deg,rgba(7,11,18,0.92),rgba(5,8,14,0.88))] px-3 py-3 shadow-[0_18px_50px_rgba(2,6,23,0.18)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 overflow-x-auto pb-1 nova-scrollbar" }, /* @__PURE__ */ React.createElement("span", { className: "flex shrink-0 items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-compass text-[10px]" }), "Sections"), items.map((item) => /* @__PURE__ */ React.createElement( - "a", - { - key: item.id, - href: `#${item.id}`, - className: "shrink-0 rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-semibold text-white/80 transition hover:border-sky-300/30 hover:bg-sky-300/10 hover:text-white" - }, - item.label - )))); +function EditorWorkspaceTabs({ tabs, activeTab, onChange, errorCounts }) { + const activeMeta = tabs.find((tab2) => tab2.id === activeTab) || tabs[0]; + return /* @__PURE__ */ React.createElement("div", { className: "sticky top-4 z-20 rounded-[24px] border border-white/10 bg-[linear-gradient(180deg,rgba(7,11,18,0.92),rgba(5,8,14,0.88))] px-3 py-3 shadow-[0_18px_50px_rgba(2,6,23,0.18)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2", role: "tablist", "aria-label": "Lesson editor sections" }, tabs.map((tab2) => { + const isActive = tab2.id === activeTab; + const errorCount = Number(errorCounts?.[tab2.id] || 0); + return /* @__PURE__ */ React.createElement( + "button", + { + key: tab2.id, + type: "button", + role: "tab", + "aria-selected": isActive, + "aria-controls": `lesson-editor-panel-${tab2.id}`, + id: `lesson-editor-tab-${tab2.id}`, + onClick: () => onChange(tab2.id), + className: [ + "inline-flex items-center gap-2 rounded-2xl border px-4 py-2.5 text-sm font-semibold transition", + isActive ? "border-sky-300/25 bg-sky-300/12 text-sky-100 ring-1 ring-sky-300/20" : "border-white/10 bg-white/[0.03] text-white/80 hover:border-sky-300/30 hover:bg-sky-300/10 hover:text-white" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${tab2.icon} text-xs` }), + /* @__PURE__ */ React.createElement("span", null, tab2.label), + errorCount > 0 ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.14em] text-rose-100" }, errorCount) : null + ); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap items-center justify-between gap-3 px-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-6 text-slate-400" }, activeMeta.description), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.16em] text-slate-500" }, activeMeta.sections.map((section) => /* @__PURE__ */ React.createElement("span", { key: section, className: "rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5" }, section.replace("lesson-", "").replace(/-/g, " ")))))); +} +function firstLessonErrorTab(errors) { + const firstKey = Object.keys(errors || {})[0]; + if (!firstKey) return null; + if (firstKey.startsWith("blocks.")) return "blocks"; + return LESSON_FIELD_TAB_MAP[firstKey] || null; +} +function lessonTabErrorCounts(errors) { + const counts = {}; + Object.keys(errors || {}).forEach((key) => { + const tabId = key.startsWith("blocks.") ? "blocks" : LESSON_FIELD_TAB_MAP[key]; + if (!tabId) return; + counts[tabId] = Number(counts[tabId] || 0) + 1; + }); + return counts; } function TextField$2({ label, value, onChange, error, hint, ...rest }) { return /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("input", { value: value ?? "", onChange, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none", ...rest }), hint ? /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, hint) : null, /* @__PURE__ */ React.createElement(FieldError$2, { message: error })); @@ -16224,7 +18951,59 @@ function TextAreaField$1({ label, value, onChange, error, rows = 4, hint }) { return /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("textarea", { value: value ?? "", onChange, rows, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), hint ? /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, hint) : null, /* @__PURE__ */ React.createElement(FieldError$2, { message: error })); } function ToggleField$2({ label, checked, onChange, help, error }) { - return /* @__PURE__ */ React.createElement("label", { className: "flex items-start gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "mt-1" }), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "block font-semibold text-white" }, label), help ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-xs leading-5 text-slate-400" }, help) : null, /* @__PURE__ */ React.createElement(FieldError$2, { message: error }))); + return /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer items-start gap-4 rounded-[28px] border px-5 py-4 transition ${checked ? "border-[#f39a24]/35 bg-[#f39a24]/10" : "border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "sr-only" }), /* @__PURE__ */ React.createElement("span", { className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border text-sm transition ${checked ? "border-[#f39a24] bg-[#f39a24] text-white" : "border-white/10 bg-[#151a29] text-transparent"}` }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-check" })), /* @__PURE__ */ React.createElement("span", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block text-base font-semibold tracking-[-0.02em] text-white" }, label), help ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-sm leading-6 text-slate-300" }, help) : null, /* @__PURE__ */ React.createElement(FieldError$2, { message: error }))); +} +function formatMissingNumbers(values) { + const items = Array.isArray(values) ? values.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value > 0) : []; + if (items.length === 0) return "No gaps right now."; + return items.join(", "); +} +function formatCourseStep(orderNum) { + const numeric = Number(orderNum); + if (!Number.isFinite(numeric) || numeric < 0) return null; + return `Step ${String(numeric + 1).padStart(2, "0")}`; +} +function normalizeCourseManagerLessons(lessons) { + return (Array.isArray(lessons) ? [...lessons] : []).sort((left, right) => { + const orderDiff = Number(left?.order_num || 0) - Number(right?.order_num || 0); + if (orderDiff !== 0) return orderDiff; + return Number(left?.id || 0) - Number(right?.id || 0); + }).map((lesson, index2) => ({ + ...lesson, + order_num: index2, + display_order: index2 + 1 + })); +} +function reorderCourseManagerLessons(lessons, draggedLessonId, targetLessonId) { + const current = normalizeCourseManagerLessons(lessons); + const draggedIndex = current.findIndex((lesson) => Number(lesson.id) === Number(draggedLessonId)); + const targetIndex = current.findIndex((lesson) => Number(lesson.id) === Number(targetLessonId)); + if (draggedIndex === -1 || targetIndex === -1 || draggedIndex === targetIndex) { + return current; + } + const nextLessons = [...current]; + const [draggedLesson] = nextLessons.splice(draggedIndex, 1); + nextLessons.splice(targetIndex, 0, draggedLesson); + return normalizeCourseManagerLessons(nextLessons); +} +function moveCourseManagerLesson(lessons, lessonId, direction) { + const current = normalizeCourseManagerLessons(lessons); + const lessonIndex = current.findIndex((lesson) => Number(lesson.id) === Number(lessonId)); + const nextIndex = lessonIndex + direction; + if (lessonIndex === -1 || nextIndex < 0 || nextIndex >= current.length) { + return current; + } + const nextLessons = [...current]; + const [movedLesson] = nextLessons.splice(lessonIndex, 1); + nextLessons.splice(nextIndex, 0, movedLesson); + return normalizeCourseManagerLessons(nextLessons); +} +function courseManagerSignature(lessons) { + return JSON.stringify(normalizeCourseManagerLessons(lessons).map((lesson) => ({ + id: Number(lesson.id), + order_num: Number(lesson.order_num || 0), + section_id: lesson.section_id == null ? null : Number(lesson.section_id) + }))); } function slugifyLessonTitle(value) { return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 180); @@ -16232,8 +19011,28 @@ function slugifyLessonTitle(value) { function stripHtml$4(value) { return String(value || "").replace(//gi, " ").replace(//gi, " ").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim(); } +function stripMarkdown(value) { + return String(value || "").replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\s{0,3}>\s?/gm, "").replace(/^\s*([-*+]|\d+\.)\s+/gm, "").replace(/\*\*|__|\*|_|~~/g, "").replace(/^\s*\|/gm, " ").replace(/\|/g, " ").replace(/^\s*[-:]{3,}\s*$/gm, " "); +} +function convertLessonMarkdownToHtml(value) { + const markdown = String(value || "").trim(); + if (!markdown) return ""; + return String(g$1.parse(markdown, { + async: false, + gfm: true, + breaks: false + }) || "").trim(); +} +function convertLessonHtmlToMarkdown(value) { + const html2 = String(value || "").trim(); + if (!html2) return ""; + if (!lessonMarkdownTurndown) { + return stripHtml$4(html2); + } + return lessonMarkdownTurndown.turndown(html2).trim(); +} function countWords(value) { - const text2 = stripHtml$4(value); + const text2 = stripMarkdown(stripHtml$4(value)); return text2 ? text2.split(/\s+/).length : 0; } function normalizeCoverPreview(value, cdnBaseUrl) { @@ -16364,12 +19163,20 @@ function buildLessonPayload(data) { category_id: data.category_id === "" || data.category_id == null ? null : Number(data.category_id), title: String(data.title || ""), slug: String(data.slug || ""), + lesson_number: data.lesson_number === "" || data.lesson_number == null ? "" : Number(data.lesson_number), + course_order: data.course_order === "" || data.course_order == null ? "" : Number(data.course_order), + course_ids: Array.isArray(data.course_ids) ? data.course_ids.map((courseId) => Number(courseId)).filter((courseId) => Number.isInteger(courseId) && courseId > 0) : [], + series_name: String(data.series_name || ""), excerpt: String(data.excerpt || ""), content: String(data.content || ""), + content_markdown: String(data.content_markdown || ""), + content_source: String(data.content_source || "html"), difficulty: String(data.difficulty || ""), access_level: String(data.access_level || ""), lesson_type: String(data.lesson_type || ""), cover_image: String(data.cover_image || ""), + article_cover_image: String(data.article_cover_image || ""), + tags: String(data.tags || ""), video_url: String(data.video_url || ""), reading_minutes: data.reading_minutes === "" || data.reading_minutes == null ? "" : Number(data.reading_minutes), published_at: data.published_at || "", @@ -16398,7 +19205,13 @@ function parseLessonImport(rawText, categoryOptions) { }; if (parsed.title != null) apply("title", String(parsed.title)); if (parsed.slug != null) apply("slug", String(parsed.slug)); + if (parsed.lesson_number != null) apply("lesson_number", String(parsed.lesson_number)); + if (parsed.course_order != null) apply("course_order", String(parsed.course_order)); + if (parsed.series_name != null) apply("series_name", String(parsed.series_name)); if (parsed.excerpt != null) apply("excerpt", String(parsed.excerpt)); + if (parsed.content_markdown != null) apply("content_markdown", String(parsed.content_markdown)); + if (parsed.markdown != null && parsed.content_markdown == null) apply("content_markdown", String(parsed.markdown)); + if (parsed.md != null && parsed.content_markdown == null && parsed.markdown == null) apply("content_markdown", String(parsed.md)); if (parsed.content != null) apply("content", String(parsed.content)); if (parsed.body != null && parsed.content == null) apply("content", String(parsed.body)); if (parsed.html != null && parsed.content == null && parsed.body == null) apply("content", String(parsed.html)); @@ -16410,6 +19223,10 @@ function parseLessonImport(rawText, categoryOptions) { if (parsed.cover_image != null) apply("cover_image", String(parsed.cover_image)); if (parsed.cover != null && parsed.cover_image == null) apply("cover_image", String(parsed.cover)); if (parsed.cover_url != null && parsed.cover_image == null && parsed.cover == null) apply("cover_image", String(parsed.cover_url)); + if (parsed.article_cover_image != null) apply("article_cover_image", String(parsed.article_cover_image)); + if (parsed.article_cover != null && parsed.article_cover_image == null) apply("article_cover_image", String(parsed.article_cover)); + if (parsed.article_cover_url != null && parsed.article_cover_image == null && parsed.article_cover == null) apply("article_cover_image", String(parsed.article_cover_url)); + if (parsed.tags != null) apply("tags", Array.isArray(parsed.tags) ? parsed.tags.join(", ") : String(parsed.tags)); if (parsed.video_url != null) apply("video_url", String(parsed.video_url)); if (parsed.reading_minutes != null) apply("reading_minutes", String(parsed.reading_minutes)); if (parsed.published_at != null) apply("published_at", String(parsed.published_at)); @@ -16433,13 +19250,13 @@ function JsonImportDialog$1({ open, value, error, onChange, onClose, onApply }) const backdropRef = reactExports.useRef(null); reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [onClose, open]); if (!open) return null; return reactDomExports.createPortal( @@ -16464,48 +19281,198 @@ function JsonImportDialog$1({ open, value, error, onChange, onClose, onApply }) placeholder: '{\n "title": "Prompt engineering for cleaner scene direction",\n "excerpt": "Short summary...",\n "content": "

    Rich HTML body...

    ",\n "category": "Prompting",\n "difficulty": "beginner"\n}', className: "rounded-[24px] border border-white/10 bg-slate-950/80 px-4 py-4 font-mono text-sm leading-6 text-slate-100 outline-none" } - ), error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, error) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Accepted keys"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "title, slug, excerpt"), /* @__PURE__ */ React.createElement("p", null, "content, body, html"), /* @__PURE__ */ React.createElement("p", null, "category_id, category_slug, category"), /* @__PURE__ */ React.createElement("p", null, "difficulty, access_level, lesson_type"), /* @__PURE__ */ React.createElement("p", null, "cover_image, cover, cover_url, video_url"), /* @__PURE__ */ React.createElement("p", null, "reading_minutes, published_at"), /* @__PURE__ */ React.createElement("p", null, "seo_title, seo_description, featured, active")))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onClose?.(), className: "inline-flex items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/70 transition hover:bg-white/[0.08] hover:text-white" }, "Cancel"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onApply?.(), className: "inline-flex items-center justify-center rounded-full border border-sky-300/25 bg-sky-400/90 px-4 py-2 text-sm font-semibold text-slate-950 transition hover:brightness-110" }, "Apply JSON"))) + ), error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, error) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Accepted keys"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "title, slug, excerpt"), /* @__PURE__ */ React.createElement("p", null, "lesson_number, course_order, series_name"), /* @__PURE__ */ React.createElement("p", null, "content_markdown, markdown, md"), /* @__PURE__ */ React.createElement("p", null, "content, body, html"), /* @__PURE__ */ React.createElement("p", null, "category_id, category_slug, category"), /* @__PURE__ */ React.createElement("p", null, "difficulty, access_level, lesson_type"), /* @__PURE__ */ React.createElement("p", null, "cover_image, cover, cover_url"), /* @__PURE__ */ React.createElement("p", null, "article_cover_image, article_cover, article_cover_url"), /* @__PURE__ */ React.createElement("p", null, "tags"), /* @__PURE__ */ React.createElement("p", null, "video_url"), /* @__PURE__ */ React.createElement("p", null, "reading_minutes, published_at"), /* @__PURE__ */ React.createElement("p", null, "seo_title, seo_description, featured, active")))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onClose?.(), className: "inline-flex items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/70 transition hover:bg-white/[0.08] hover:text-white" }, "Cancel"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onApply?.(), className: "inline-flex items-center justify-center rounded-full border border-sky-300/25 bg-sky-400/90 px-4 py-2 text-sm font-semibold text-slate-950 transition hover:brightness-110" }, "Apply JSON"))) + ), + document.body + ); +} +function MarkdownImportDialog({ open, value, error, onChange, onClose, onApply }) { + const backdropRef = reactExports.useRef(null); + reactExports.useEffect(() => { + if (!open) return void 0; + const handleKeyDown2 = (event) => { + if (event.key === "Escape") { + onClose?.(); + } + }; + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); + }, [onClose, open]); + if (!open) return null; + return reactDomExports.createPortal( + /* @__PURE__ */ React.createElement( + "div", + { + ref: backdropRef, + className: "fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md", + onClick: (event) => { + if (event.target === backdropRef.current) { + onClose?.(); + } + }, + role: "presentation" + }, + /* @__PURE__ */ React.createElement("div", { className: "w-full max-w-4xl overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]" }, /* @__PURE__ */ React.createElement("div", { className: "border-b border-white/[0.06] bg-white/[0.02] px-6 py-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35" }, "Markdown Import"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-lg font-semibold text-white" }, "Paste lesson Markdown"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-white/65" }, "Paste Markdown here and apply it to regenerate the lesson HTML. This overwrites the current article body.")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 px-6 py-5 xl:grid-cols-[minmax(0,1fr)_280px]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement( + "textarea", + { + value, + onChange: (event) => onChange?.(event.target.value), + rows: 18, + placeholder: "## Introduction\n\nPaste Markdown here to regenerate the lesson body.", + spellCheck: false, + className: "rounded-[24px] border border-white/10 bg-slate-950/80 px-4 py-4 font-mono text-sm leading-6 text-slate-100 outline-none" + } + ), error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, error) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Result"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "The Markdown is stored in ", /* @__PURE__ */ React.createElement("span", { className: "font-mono text-slate-200" }, "content_markdown"), "."), /* @__PURE__ */ React.createElement("p", null, "The HTML editor is regenerated from it."), /* @__PURE__ */ React.createElement("p", null, "The visual lesson preview updates immediately."), /* @__PURE__ */ React.createElement("p", null, "Use the HTML button inside the editor for source-level HTML edits.")))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onClose?.(), className: "inline-flex items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/70 transition hover:bg-white/[0.08] hover:text-white" }, "Cancel"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onApply?.(), className: "inline-flex items-center justify-center rounded-full border border-sky-300/25 bg-sky-400/90 px-4 py-2 text-sm font-semibold text-slate-950 transition hover:brightness-110" }, "Apply Markdown"))) ), document.body ); } function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method, editorContext = {} }) { + const recordContentMarkdown = String(record.content_markdown || ""); + String(record.content || ""); const cdnBaseUrl = editorContext.coverCdnBaseUrl || ""; - const form = G({ + const initialContentMarkdown = recordContentMarkdown.trim() !== "" ? recordContentMarkdown : ""; + const form = G$1({ ...record, + content: String(record.content || convertLessonMarkdownToHtml(initialContentMarkdown) || ""), + content_markdown: initialContentMarkdown, + content_source: recordContentMarkdown.trim() !== "" ? "markdown" : "html", + lesson_number: record.lesson_number === "" || record.lesson_number == null ? "" : String(record.lesson_number), + course_order: record.course_order === "" || record.course_order == null ? "" : String(record.course_order), + series_name: String(record.series_name || ""), + article_cover_image: String(record.article_cover_image || ""), + course_ids: Array.isArray(record.course_ids) ? record.course_ids.map((courseId) => String(courseId)) : [], + tags: String(record.tags || ""), category_id: normalizeCategoryValue(record.category_id), blocks: normalizeComparisonBlocks(record.blocks, cdnBaseUrl) }); const [coverPreviewUrl, setCoverPreviewUrl] = reactExports.useState(record.cover_image_url || normalizeCoverPreview(record.cover_image, editorContext.coverCdnBaseUrl)); const [stagedCoverPath, setStagedCoverPath] = reactExports.useState(""); + const [articleCoverPreviewUrl, setArticleCoverPreviewUrl] = reactExports.useState(record.article_cover_image_url || normalizeCoverPreview(record.article_cover_image, editorContext.coverCdnBaseUrl)); + const [stagedArticleCoverPath, setStagedArticleCoverPath] = reactExports.useState(""); const [categories, setCategories] = reactExports.useState(Array.isArray(editorContext.categories) ? editorContext.categories : []); const [jsonImportOpen, setJsonImportOpen] = reactExports.useState(false); const [jsonImportValue, setJsonImportValue] = reactExports.useState(""); const [jsonImportError, setJsonImportError] = reactExports.useState(""); + const [markdownImportOpen, setMarkdownImportOpen] = reactExports.useState(false); + const [markdownImportValue, setMarkdownImportValue] = reactExports.useState(""); + const [markdownImportError, setMarkdownImportError] = reactExports.useState(""); const [categoryError, setCategoryError] = reactExports.useState(""); const [categorySaving, setCategorySaving] = reactExports.useState(false); + const [isBrowserFullscreen, setIsBrowserFullscreen] = reactExports.useState(() => typeof document !== "undefined" && Boolean(document.fullscreenElement)); + const [isEditorFullHeight, setIsEditorFullHeight] = reactExports.useState(false); + const [activeTab, setActiveTab] = reactExports.useState("write"); const [categoryDraft, setCategoryDraft] = reactExports.useState({ type: "lesson", name: "", slug: "", description: "", order_num: 0, active: true }); const slugTouchedRef = reactExports.useRef(Boolean(String(record.slug || "").trim())); + const lessonNumberAutofillRef = reactExports.useRef(false); + const courseOrderAutofillRef = reactExports.useRef(false); const difficultyField = reactExports.useMemo(() => getField$1(fields, "difficulty"), [fields]); const accessField = reactExports.useMemo(() => getField$1(fields, "access_level"), [fields]); - const bodyWordCount = reactExports.useMemo(() => countWords(form.data.content), [form.data.content]); + const bodyWordCount = reactExports.useMemo(() => countWords(form.data.content_markdown || form.data.content), [form.data.content, form.data.content_markdown]); + const estimatedReadingMinutes = reactExports.useMemo(() => Math.max(1, Math.ceil(bodyWordCount / 180)), [bodyWordCount]); const excerptLength = String(form.data.excerpt || "").length; + const deferredArticlePreviewHtml = reactExports.useDeferredValue(form.data.content || ""); + const tabErrorCounts = reactExports.useMemo(() => lessonTabErrorCounts(form.errors), [form.errors]); + const numberingContext = reactExports.useMemo(() => editorContext.numbering || {}, [editorContext.numbering]); + const courseOptions = reactExports.useMemo(() => Array.isArray(editorContext.courses) ? editorContext.courses : [], [editorContext.courses]); + const selectedCourses = reactExports.useMemo(() => { + const selectedIds = new Set((Array.isArray(form.data.course_ids) ? form.data.course_ids : []).map((courseId) => String(courseId))); + return courseOptions.filter((course) => selectedIds.has(String(course.value))); + }, [courseOptions, form.data.course_ids]); + const currentLessonId = reactExports.useMemo(() => Number(editorContext.currentLessonId || 0), [editorContext.currentLessonId]); + const [courseManagerDrafts, setCourseManagerDrafts] = reactExports.useState({}); + const [draggedCourseLesson, setDraggedCourseLesson] = reactExports.useState(null); + const [courseSaveProcessing, setCourseSaveProcessing] = reactExports.useState({}); + const revisions = reactExports.useMemo(() => Array.isArray(editorContext.revisions) ? editorContext.revisions : [], [editorContext.revisions]); + const [revisionFieldSelections, setRevisionFieldSelections] = reactExports.useState({}); const csrfToken2 = reactExports.useMemo(() => { if (typeof document === "undefined") return ""; return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || ""; }, []); + const handleMarkdownContentChange = (nextMarkdown) => { + const nextHtml = convertLessonMarkdownToHtml(nextMarkdown); + reactExports.startTransition(() => { + form.setData("content_source", "markdown"); + form.setData("content_markdown", nextMarkdown); + form.setData("content", nextHtml); + }); + }; + const handleRichContentChange = (nextHtml) => { + reactExports.startTransition(() => { + form.setData("content", nextHtml); + if (form.data.content_source === "markdown") { + form.setData("content_markdown", convertLessonHtmlToMarkdown(nextHtml)); + return; + } + form.setData("content_markdown", ""); + }); + }; + reactExports.useEffect(() => { + void loadLessonMarkdownTurndown(); + }, []); reactExports.useEffect(() => { if (slugTouchedRef.current) return; form.setData("slug", slugifyLessonTitle(form.data.title)); }, [form.data.title]); + reactExports.useEffect(() => { + if (typeof document === "undefined") return void 0; + const handleFullscreenChange = () => { + setIsBrowserFullscreen(Boolean(document.fullscreenElement)); + }; + document.addEventListener("fullscreenchange", handleFullscreenChange); + return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); + }, []); + reactExports.useEffect(() => { + if (typeof document === "undefined") return void 0; + const previousOverflow = document.body.style.overflow; + if (isEditorFullHeight) { + document.body.style.overflow = "hidden"; + } + return () => { + document.body.style.overflow = previousOverflow; + }; + }, [isEditorFullHeight]); + reactExports.useEffect(() => { + const nextTab = firstLessonErrorTab(form.errors); + if (!nextTab) return; + setActiveTab(nextTab); + }, [form.errors]); + reactExports.useEffect(() => { + setCourseManagerDrafts(Object.fromEntries(selectedCourses.map((course) => [String(course.value), normalizeCourseManagerLessons(course.lessons)]))); + }, [selectedCourses]); const categoryOptions = reactExports.useMemo(() => { const next = categories.map((category) => ({ value: String(category.id), label: category.name })); return [{ value: "", label: "No category" }, ...next]; }, [categories]); + reactExports.useEffect(() => { + if (method !== "post" || lessonNumberAutofillRef.current) return; + if (String(form.data.lesson_number || "").trim() !== "") return; + const suggested = Number(numberingContext?.lesson_number?.suggested || 0); + if (!Number.isInteger(suggested) || suggested < 1) return; + lessonNumberAutofillRef.current = true; + form.setData("lesson_number", String(suggested)); + }, [form, form.data.lesson_number, method, numberingContext]); + reactExports.useEffect(() => { + if (method !== "post" || courseOrderAutofillRef.current) return; + if (String(form.data.course_order || "").trim() !== "") return; + const suggested = Number(numberingContext?.course_order?.suggested || 0); + if (!Number.isInteger(suggested) || suggested < 1) return; + courseOrderAutofillRef.current = true; + form.setData("course_order", String(suggested)); + }, [form, form.data.course_order, method, numberingContext]); + reactExports.useEffect(() => { + const nextValue = String(estimatedReadingMinutes); + if (String(form.data.reading_minutes || "") === nextValue) return; + form.setData("reading_minutes", nextValue); + }, [estimatedReadingMinutes, form, form.data.reading_minutes]); const handleManualCoverChange = (nextValue) => { form.setData("cover_image", nextValue); setCoverPreviewUrl(normalizeCoverPreview(nextValue, editorContext.coverCdnBaseUrl)); }; + const handleManualArticleCoverChange = (nextValue) => { + form.setData("article_cover_image", nextValue); + setArticleCoverPreviewUrl(normalizeCoverPreview(nextValue, editorContext.coverCdnBaseUrl)); + }; const updateBlocks = (updater) => { const currentBlocks = Array.isArray(form.data.blocks) ? form.data.blocks : []; const nextBlocks = typeof updater === "function" ? updater(currentBlocks) : updater; @@ -16559,10 +19526,56 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de if (!window.confirm("Delete this lesson?")) return; At.delete(destroyUrl); }; + const updateCourseDraft = (courseId, nextLessons) => { + setCourseManagerDrafts((current) => ({ + ...current, + [String(courseId)]: normalizeCourseManagerLessons(nextLessons) + })); + }; + const attachLessonToCourseNow = (course) => { + if (!currentLessonId) return; + At.post(course.attach_url, { + lesson_id: currentLessonId, + order_num: Number(course.next_order_num || 0), + is_required: true + }, { + preserveScroll: true, + onSuccess: () => form.setData("course_ids", Array.from(/* @__PURE__ */ new Set([...Array.isArray(form.data.course_ids) ? form.data.course_ids : [], String(course.value)]))) + }); + }; + const detachLessonFromCourseNow = (course, courseLesson) => { + At.delete(courseLesson.destroy_url, { + preserveScroll: true, + onSuccess: () => form.setData("course_ids", (Array.isArray(form.data.course_ids) ? form.data.course_ids : []).filter((courseId) => String(courseId) !== String(course.value))) + }); + }; + const saveCourseDraftOrder = (course) => { + const nextLessons = normalizeCourseManagerLessons(courseManagerDrafts[String(course.value)] || course.lessons); + setCourseSaveProcessing((current) => ({ ...current, [String(course.value)]: true })); + At.patch(course.reorder_url, { + sections: [], + lessons: nextLessons.map((lesson) => ({ + id: lesson.id, + order_num: lesson.order_num, + section_id: lesson.section_id + })) + }, { + preserveScroll: true, + onFinish: () => setCourseSaveProcessing((current) => ({ ...current, [String(course.value)]: false })) + }); + }; const applyJsonImport = () => { try { const parsed = parseLessonImport(jsonImportValue, categories); Object.entries(parsed.next).forEach(([key, value]) => { + if (key === "content_markdown") { + handleMarkdownContentChange(String(value || "")); + return; + } + if (key === "content") { + handleRichContentChange(String(value || "")); + return; + } form.setData(key, value); }); if (parsed.next.cover_image != null) { @@ -16577,6 +19590,21 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de setJsonImportError(error instanceof Error ? error.message : "Could not parse JSON."); } }; + const openMarkdownImport = () => { + setMarkdownImportValue(String(form.data.content_markdown || "")); + setMarkdownImportError(""); + setMarkdownImportOpen(true); + }; + const applyMarkdownImport = () => { + const nextMarkdown = String(markdownImportValue || "").trim(); + if (nextMarkdown === "") { + setMarkdownImportError("Paste Markdown before applying it to the lesson body."); + return; + } + handleMarkdownContentChange(nextMarkdown); + setMarkdownImportError(""); + setMarkdownImportOpen(false); + }; const createCategory = async () => { setCategorySaving(true); setCategoryError(""); @@ -16617,7 +19645,62 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de } }; const comparisonBlocks = Array.isArray(form.data.blocks) ? form.data.blocks : []; - return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se, { title: `Admin · ${title}` }), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-6 pb-16" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_34%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.94))] shadow-[0_24px_70px_rgba(2,6,23,0.34)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-white/10 px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white transition hover:bg-white/[0.08]" }, "Back to lessons"), /* @__PURE__ */ React.createElement("span", null, destroyUrl ? "Edit lesson" : "New lesson")), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.05em] text-white" }, form.data.title || "Untitled academy lesson"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-7 text-slate-300" }, "Use the same richer writing flow as the newsroom: drag in the cover, shape the article with the rich editor, and keep publishing details in the same place.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setJsonImportOpen(true), className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Import JSON"), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-2xl border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save lesson")))), /* @__PURE__ */ React.createElement(SectionNav, { items: LESSON_SECTION_NAV_ITEMS }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6" }, /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-story-setup", eyebrow: "Story setup", title: "Headline and framing", description: "Start with the lesson identity and summary, then move into the full article body.", tone: "feature" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( + const toggleBrowserFullscreen = async () => { + if (typeof document === "undefined") return; + try { + if (document.fullscreenElement) { + await document.exitFullscreen(); + return; + } + await document.documentElement.requestFullscreen(); + } catch { + } + }; + const bodyEditorActions = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.14em] text-white" }, bodyWordCount.toLocaleString(), " words"), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: openMarkdownImport, + className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.08]" + }, + "Import Markdown" + ), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => setIsEditorFullHeight((current) => !current), + className: `rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] transition ${isEditorFullHeight ? "border-sky-300/35 bg-sky-300/12 text-sky-100" : "border-white/10 bg-white/[0.04] text-white hover:bg-white/[0.08]"}` + }, + isEditorFullHeight ? "Normal height" : "Full-height editor" + ), /* @__PURE__ */ React.createElement( + "button", + { + type: "button", + onClick: () => void toggleBrowserFullscreen(), + className: `rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] transition ${isBrowserFullscreen ? "border-[#ff9e8c]/35 bg-[#ff9e8c]/12 text-[#ffd5cd]" : "border-white/10 bg-white/[0.04] text-white hover:bg-white/[0.08]"}` + }, + isBrowserFullscreen ? "Exit browser fullscreen" : "Browser fullscreen" + )); + const visibleSections = reactExports.useMemo(() => new Set(LESSON_EDITOR_TABS.find((tab2) => tab2.id === activeTab)?.sections || []), [activeTab]); + const isSectionVisible = (sectionId) => visibleSections.has(sectionId); + const activeTabMeta = reactExports.useMemo(() => LESSON_EDITOR_TABS.find((tab2) => tab2.id === activeTab) || LESSON_EDITOR_TABS[0], [activeTab]); + const sectionClassName = (sectionId, className = "") => `${isSectionVisible(sectionId) ? "" : "hidden"} ${className}`.trim(); + const showWriteCompanion = activeTab === "write" && !isEditorFullHeight; + const showSupportRail = showWriteCompanion || isSectionVisible("lesson-preview"); + const lessonStatusItems = [ + { label: "Title", ready: String(form.data.title || "").trim() !== "" }, + { label: "Excerpt", ready: String(form.data.excerpt || "").trim() !== "" }, + { label: "Body", ready: String(form.data.content || form.data.content_markdown || "").trim() !== "" }, + { label: "Slug", ready: String(form.data.slug || "").trim() !== "" } + ]; + const lessonPathPreview = form.data.slug ? `/academy/${form.data.slug}` : "/academy/lesson-slug"; + const restoreLessonRevision = (revision, field = "") => { + if (!revision?.restore_url) return; + const message = field ? `Restore ${LESSON_REVISION_FIELD_OPTIONS.find((option) => option.value === field)?.label || field} from revision #${revision.id}? A new safety revision will be created first.` : `Restore the full lesson from revision #${revision.id}? A new safety revision will be created first.`; + if (!window.confirm(message)) return; + At.post(revision.restore_url, field ? { field } : {}, { preserveScroll: true }); + }; + return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${title}` }), isEditorFullHeight ? /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-[110] bg-[#02040add]/90 backdrop-blur-md", "aria-hidden": "true" }) : null, /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-6 pb-16" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_34%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.94))] shadow-[0_24px_70px_rgba(2,6,23,0.34)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-white/10 px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white transition hover:bg-white/[0.08]" }, "Back to lessons"), /* @__PURE__ */ React.createElement("span", null, destroyUrl ? "Edit lesson" : "New lesson")), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.05em] text-white" }, form.data.title || "Untitled academy lesson"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-7 text-slate-300" }, "Use the same richer writing flow as the newsroom: drag in the cover, shape the article with the rich editor, and keep publishing details in the same place.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setJsonImportOpen(true), className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Import JSON"), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-2xl border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save lesson")))), /* @__PURE__ */ React.createElement(EditorWorkspaceTabs, { tabs: LESSON_EDITOR_TABS, activeTab, onChange: setActiveTab, errorCounts: tabErrorCounts }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.14)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Current workspace"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, activeTabMeta.label), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-6 text-slate-400" }, activeTabMeta.description)), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Words"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, bodyWordCount.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Excerpt"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, excerptLength, "/800")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Errors"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Object.keys(form.errors || {}).length))))), /* @__PURE__ */ React.createElement("div", { className: showSupportRail ? "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-start" : "grid gap-6" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6", role: "tabpanel", id: `lesson-editor-panel-${activeTab}`, "aria-labelledby": `lesson-editor-tab-${activeTab}` }, activeTab === "preview" ? /* @__PURE__ */ React.createElement(SectionCard$5, { eyebrow: "Preview mode", title: "Rendered lesson review", description: "Use this tab to scan the public-facing lesson card, article imagery, and rendered article body without the rest of the form in the way.", tone: "feature" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Hero image"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, coverPreviewUrl ? "Ready" : "Missing", " hero artwork for lesson cards and social previews.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Article image"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, articleCoverPreviewUrl ? "Ready" : "Missing", " inline article cover shown before the lesson body.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Body length"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, bodyWordCount.toLocaleString(), " words currently in the lesson body.")))) : null, /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-story-setup", eyebrow: "Story setup", title: "Headline and framing", description: "Start with the lesson identity and summary, then move into the full article body.", tone: "feature", className: sectionClassName("lesson-story-setup") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement( TextField$2, { label: "Title", @@ -16642,30 +19725,44 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de placeholder: "prompt-engineering-for-cleaner-scene-direction", maxLength: 180 } - ), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "The slug follows the title until you override it manually."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.slug }))), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "Excerpt"), /* @__PURE__ */ React.createElement("span", null, excerptLength, "/800")), /* @__PURE__ */ React.createElement("textarea", { value: form.data.excerpt, onChange: (event) => form.setData("excerpt", event.target.value), rows: 5, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none", placeholder: "Summarize what the lesson teaches, why it matters, and what a creator will walk away with." }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "This is the short summary used in cards, internal lists, and metadata surfaces."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.excerpt }))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-body-editor", eyebrow: "Main article", title: "Lesson body editor", description: "Write the tutorial in the same richer editing surface used for newsroom articles.", actions: /* @__PURE__ */ React.createElement("div", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.14em] text-white" }, bodyWordCount.toLocaleString(), " words") }, /* @__PURE__ */ React.createElement("div", { className: "grid min-w-0 gap-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement( - RichTextEditor, + ), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "The slug follows the title until you override it manually."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.slug }))), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "Excerpt"), /* @__PURE__ */ React.createElement("span", null, excerptLength, "/800")), /* @__PURE__ */ React.createElement("textarea", { value: form.data.excerpt, onChange: (event) => form.setData("excerpt", event.target.value), rows: 5, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none", placeholder: "Summarize what the lesson teaches, why it matters, and what a creator will walk away with." }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "This is the short summary used in cards, internal lists, and metadata surfaces."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.excerpt }))), /* @__PURE__ */ React.createElement( + SectionCard$5, { - content: form.data.content, - onChange: (nextValue) => form.setData("content", nextValue), - placeholder: "Open with the problem, explain the workflow step by step, and use headings, media, and links where the lesson needs structure.", - error: form.errors.content, - minHeight: 24, - autofocus: false, - advancedNews: true, - mediaSupport: { - uploadUrl: editorContext.bodyMediaUploadUrl, - deleteUrl: editorContext.bodyMediaDeleteUrl, - assetsUrl: editorContext.bodyMediaAssetsUrl, - slot: "body" + id: "lesson-body-editor", + eyebrow: "Main article", + title: "Lesson body editor", + description: "Write the tutorial in the same richer editing surface used for newsroom articles.", + actions: bodyEditorActions, + className: sectionClassName("lesson-body-editor", isEditorFullHeight ? "fixed inset-4 z-[120] flex min-h-0 flex-col overflow-hidden border-sky-300/20 shadow-[0_32px_100px_rgba(2,6,23,0.72)]" : ""), + contentClassName: isEditorFullHeight ? "flex min-h-0 flex-1 flex-col" : "" + }, + /* @__PURE__ */ React.createElement("div", { className: `grid min-w-0 gap-3 text-sm text-slate-300 ${isEditorFullHeight ? "min-h-0 flex-1" : ""}`.trim() }, /* @__PURE__ */ React.createElement( + RichTextEditor, + { + content: form.data.content, + onChange: handleRichContentChange, + placeholder: "Open with the problem, explain the workflow step by step, and use headings, media, and links where the lesson needs structure.", + error: form.errors.content, + minHeight: isEditorFullHeight ? 30 : 24, + maxHeightRem: isEditorFullHeight ? 72 : 42, + autofocus: false, + advancedNews: true, + mediaSupport: { + uploadUrl: editorContext.bodyMediaUploadUrl, + deleteUrl: editorContext.bodyMediaDeleteUrl, + assetsUrl: editorContext.bodyMediaAssetsUrl, + slot: "body" + } } - } - ), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-xs leading-6 text-slate-400" }, "Tutorial workflow suggestion: define the outcome, break the process into clear steps, call out traps or quality checks, then finish with a practical next move."))), /* @__PURE__ */ React.createElement( + )) + ), /* @__PURE__ */ React.createElement( SectionCard$5, { id: "lesson-ai-comparisons", eyebrow: "Structured blocks", title: "AI model comparisons", description: "Add reusable same-prompt comparison blocks without burying the data inside the lesson HTML body.", + className: sectionClassName("lesson-ai-comparisons"), actions: /* @__PURE__ */ React.createElement("button", { type: "button", onClick: addComparisonBlock, className: "rounded-full border border-[#ff9e8c]/25 bg-[#ff9e8c]/12 px-4 py-2 text-sm font-semibold text-[#ffd5cd]" }, "+ Add AI Comparison") }, /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, comparisonBlocks.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-black/20 px-5 py-6 text-sm leading-7 text-slate-400" }, "No comparison blocks yet. Add one when a lesson needs the same prompt analyzed across multiple AI image tools.") : comparisonBlocks.map((block, blockIndex) => /* @__PURE__ */ React.createElement("div", { key: block.client_key, className: "rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(255,158,140,0.12),transparent_34%),linear-gradient(180deg,rgba(15,23,42,0.82),rgba(6,10,18,0.95))] p-5 shadow-[0_24px_60px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3 border-b border-white/10 pb-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-[#ffc0b4]" }, "AI Model Comparison"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-xl font-semibold tracking-[-0.03em] text-white" }, block.payload?.title || block.title || DEFAULT_AI_COMPARISON_TITLE)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeBlock(block.client_key), className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-3 py-1.5 text-xs font-semibold text-rose-100" }, "Remove block"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement( @@ -16900,7 +19997,43 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de help: "Inactive results stay saved but do not render on the public lesson page." } ))))))))) - ), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-publishing", eyebrow: "Publishing", title: "Placement and visibility", description: "Set the lesson metadata, schedule, and discovery fields before it goes live." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Difficulty", value: form.data.difficulty || "", onChange: (nextValue) => form.setData("difficulty", String(nextValue || "")), options: difficultyField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.difficulty })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Access", value: form.data.access_level || "", onChange: (nextValue) => form.setData("access_level", String(nextValue || "")), options: accessField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.access_level }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "Lesson type", value: form.data.lesson_type, onChange: (event) => form.setData("lesson_type", event.target.value), error: form.errors.lesson_type, placeholder: "article, video, walkthrough" }), /* @__PURE__ */ React.createElement(TextField$2, { label: "Reading minutes", value: form.data.reading_minutes, onChange: (event) => form.setData("reading_minutes", event.target.value), error: form.errors.reading_minutes, type: "number", min: "1", max: "999" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Category", value: form.data.category_id || "", onChange: (nextValue) => form.setData("category_id", String(nextValue || "")), options: categoryOptions, searchable: false, className: "bg-black/20", error: form.errors.category_id })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Publish at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.data.published_at || "", onChange: (nextValue) => form.setData("published_at", nextValue || ""), clearable: true, className: "bg-black/20" }), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.published_at }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(ToggleField$2, { label: "Featured", checked: Boolean(form.data.featured), onChange: (event) => form.setData("featured", event.target.checked), help: "Highlight this lesson in featured academy surfaces.", error: form.errors.featured }), /* @__PURE__ */ React.createElement(ToggleField$2, { label: "Active", checked: Boolean(form.data.active), onChange: (event) => form.setData("active", event.target.checked), help: "Keep inactive lessons hidden until the draft is ready.", error: form.errors.active }))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-seo", eyebrow: "SEO", title: "Search metadata", description: "Keep the lesson search-ready without stuffing the headline." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "SEO title", value: form.data.seo_title, onChange: (event) => form.setData("seo_title", event.target.value), error: form.errors.seo_title, maxLength: 180, placeholder: "Optional search title" }), /* @__PURE__ */ React.createElement(TextField$2, { label: "Video URL", value: form.data.video_url, onChange: (event) => form.setData("video_url", event.target.value), error: form.errors.video_url, placeholder: "Optional lesson video URL" })), /* @__PURE__ */ React.createElement(TextAreaField$1, { label: "SEO description", value: form.data.seo_description, onChange: (event) => form.setData("seo_description", event.target.value), error: form.errors.seo_description, rows: 4, hint: "Keep this tighter than the excerpt and focused on search intent." })), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-categories", eyebrow: "Lesson categories", title: "Create category inline", description: "Add lesson categories without leaving the writing flow.", actions: /* @__PURE__ */ React.createElement("a", { href: editorContext.categoryManageUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Manage all categories") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: categoryDraft.name, onChange: (event) => setCategoryDraft((current) => ({ ...current, name: event.target.value })), placeholder: "Category name", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: categoryDraft.slug, onChange: (event) => setCategoryDraft((current) => ({ ...current, slug: event.target.value })), placeholder: "Optional slug", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("textarea", { value: categoryDraft.description, onChange: (event) => setCategoryDraft((current) => ({ ...current, description: event.target.value })), rows: 3, placeholder: "Description", className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("input", { type: "number", value: categoryDraft.order_num, min: "0", onChange: (event) => setCategoryDraft((current) => ({ ...current, order_num: event.target.value })), className: "w-28 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("label", { className: "flex items-center gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: categoryDraft.active, onChange: (event) => setCategoryDraft((current) => ({ ...current, active: event.target.checked })) }), " Active"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => void createCategory(), disabled: categorySaving, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100" }, categorySaving ? "Creating..." : "Create category")), categoryError ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, categoryError) : null), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, (categories || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-500" }, "No lesson categories yet.") : categories.map((category) => /* @__PURE__ */ React.createElement("div", { key: category.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, category.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.14em] text-slate-500" }, category.slug), category.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, category.description) : null), /* @__PURE__ */ React.createElement("a", { href: category.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Edit")))))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6 xl:sticky xl:top-6 xl:self-start" }, /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-cover", eyebrow: "Cover image", title: "Hero asset", description: "Use drag and drop for the lesson image, or paste a direct URL when you already have one." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement( + ), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-course-numbering", eyebrow: "Course placement", title: "Courses and numbering", description: "Keep series numbering and course placement in one workspace so guided lesson management stays separate from publishing.", className: sectionClassName("lesson-course-numbering") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "Series name", value: form.data.series_name, onChange: (event) => form.setData("series_name", event.target.value), error: form.errors.series_name, maxLength: 120, placeholder: "AI Art Basics", hint: "Shown before the lesson number on public pages. Leave empty if this lesson is not part of a named series." })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "Reading minutes", value: form.data.reading_minutes, error: form.errors.reading_minutes, type: "number", min: "1", max: "999", readOnly: true, hint: `Auto-calculated from the lesson body. Current estimate: ${estimatedReadingMinutes} min for ${bodyWordCount.toLocaleString()} words.` }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "Lesson number"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => form.setData("lesson_number", String(numberingContext?.lesson_number?.suggested || "")), className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold text-white" }, "Use ", numberingContext?.lesson_number?.suggested || "next")), /* @__PURE__ */ React.createElement("input", { value: form.data.lesson_number ?? "", onChange: (event) => form.setData("lesson_number", event.target.value), type: "number", min: "1", placeholder: String(numberingContext?.lesson_number?.suggested || 3), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "Visible public numbering like Lesson 03. Suggested next slot: ", numberingContext?.lesson_number?.suggested || 1, ". Missing numbers: ", formatMissingNumbers(numberingContext?.lesson_number?.missing), "."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.lesson_number }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "Course order"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => form.setData("course_order", String(numberingContext?.course_order?.suggested || "")), className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold text-white" }, "Use ", numberingContext?.course_order?.suggested || "next")), /* @__PURE__ */ React.createElement("input", { value: form.data.course_order ?? "", onChange: (event) => form.setData("course_order", event.target.value), type: "number", min: "1", placeholder: String(numberingContext?.course_order?.suggested || 3), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "Controls standalone lesson list sorting and previous or next lesson navigation. Suggested next slot: ", numberingContext?.course_order?.suggested || 1, ". Missing numbers: ", formatMissingNumbers(numberingContext?.course_order?.missing), "."), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.course_order })))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Lesson numbering"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold text-sky-100" }, "Suggested ", numberingContext?.lesson_number?.suggested || 1), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold text-slate-200" }, numberingContext?.lesson_number?.used_count || 0, " used"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold text-slate-200" }, "Highest ", numberingContext?.lesson_number?.highest || 0)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, "Missing lesson numbers: ", formatMissingNumbers(numberingContext?.lesson_number?.missing))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Course order guidance"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold text-amber-100" }, "Suggested ", numberingContext?.course_order?.suggested || 1), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold text-slate-200" }, numberingContext?.course_order?.used_count || 0, " used"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold text-slate-200" }, "Highest ", numberingContext?.course_order?.highest || 0)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, "Missing course-order slots: ", formatMissingNumbers(numberingContext?.course_order?.missing)))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement( + NovaSelect, + { + multi: true, + label: "Courses", + value: form.data.course_ids || [], + onChange: (nextValue) => form.setData("course_ids", Array.isArray(nextValue) ? nextValue.map((courseId) => String(courseId)) : []), + options: courseOptions, + className: "bg-black/20", + error: form.errors.course_ids + } + ), /* @__PURE__ */ React.createElement("p", { className: "text-xs leading-6 text-slate-400" }, "Attach this lesson to one or more existing courses. You can also attach or remove the current lesson immediately from the course manager cards below."))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-course-manager", eyebrow: "Course manager", title: "Manage course placement", description: "Attach the current lesson, drag to reorder it within each selected course, and save the updated path without leaving this lesson page.", className: sectionClassName("lesson-course-manager") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, selectedCourses.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-black/20 px-5 py-5 text-sm leading-7 text-slate-400" }, "Select one or more courses above to manage where this lesson sits in guided learning paths.") : selectedCourses.map((course) => { + const currentCourseLesson = Array.isArray(course.lessons) ? course.lessons.find((lesson) => lesson.is_current) : null; + const nextStepLabel = formatCourseStep(course.next_order_num); + const draftLessons = normalizeCourseManagerLessons(courseManagerDrafts[String(course.value)] || course.lessons); + const draftIsDirty = courseManagerSignature(draftLessons) !== courseManagerSignature(course.lessons); + const courseIsSaving = Boolean(courseSaveProcessing[String(course.value)]); + return /* @__PURE__ */ React.createElement("div", { key: course.value, className: "rounded-[26px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("p", { className: "text-lg font-semibold tracking-[-0.03em] text-white" }, course.label), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, course.status), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, course.lesson_count, " lessons"), draftIsDirty ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Unsaved order") : null), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, currentCourseLesson ? `${currentCourseLesson.title} is currently attached in ${formatCourseStep(currentCourseLesson.order_num) || `slot ${currentCourseLesson.display_order}`}.` : `This lesson is not attached yet. Add it now at ${nextStepLabel || `slot ${Number(course.next_order_num || 0) + 1}`}.`)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, currentLessonId > 0 && !currentCourseLesson ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => attachLessonToCourseNow(course), className: "rounded-full border border-[#f39a24]/25 bg-[#f39a24]/12 px-3 py-1.5 text-xs font-semibold text-[#ffd5cd]" }, "Add this lesson now") : null, currentCourseLesson ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => detachLessonFromCourseNow(course, currentCourseLesson), className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-3 py-1.5 text-xs font-semibold text-rose-100" }, "Remove from course") : null, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateCourseDraft(course.value, course.lessons), disabled: !draftIsDirty || courseIsSaving, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white disabled:opacity-40" }, "Reset order"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => saveCourseDraftOrder(course), disabled: !draftIsDirty || courseIsSaving, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-3 py-1.5 text-xs font-semibold text-sky-100 disabled:opacity-40" }, courseIsSaving ? "Saving..." : "Save order"), /* @__PURE__ */ React.createElement("a", { href: course.builder_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Open builder"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-2" }, draftLessons.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/10 bg-white/[0.03] px-4 py-4 text-sm text-slate-500" }, "No lessons are attached to this course yet.") : draftLessons.map((lesson, lessonIndex) => /* @__PURE__ */ React.createElement( + "div", + { + key: lesson.id, + draggable: true, + onDragStart: () => setDraggedCourseLesson({ courseId: String(course.value), lessonId: lesson.id }), + onDragEnd: () => setDraggedCourseLesson(null), + onDragOver: (event) => event.preventDefault(), + onDrop: (event) => { + event.preventDefault(); + if (!draggedCourseLesson || draggedCourseLesson.courseId !== String(course.value)) return; + updateCourseDraft(course.value, reorderCourseManagerLessons(draftLessons, draggedCourseLesson.lessonId, lesson.id)); + setDraggedCourseLesson(null); + }, + className: `flex flex-wrap items-center justify-between gap-3 rounded-2xl border px-4 py-3 transition ${lesson.is_current ? "border-[#f39a24]/30 bg-[#f39a24]/10" : "border-white/10 bg-white/[0.03]"} ${draggedCourseLesson?.courseId === String(course.value) && Number(draggedCourseLesson.lessonId) === Number(lesson.id) ? "opacity-60" : ""}` + }, + /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, formatCourseStep(lesson.order_num) || `#${lesson.display_order}`), lesson.formatted_lesson_number ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, lesson.formatted_lesson_number) : null, lesson.section_title ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, lesson.section_title) : null, lesson.is_current ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#f39a24]/25 bg-[#f39a24]/12 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-[#ffd5cd]" }, "This lesson") : null), /* @__PURE__ */ React.createElement("p", { className: "mt-2 truncate text-sm font-semibold text-white" }, lesson.title)), + /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateCourseDraft(course.value, moveCourseManagerLesson(draftLessons, lesson.id, -1)), disabled: lessonIndex === 0, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white disabled:opacity-40" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateCourseDraft(course.value, moveCourseManagerLesson(draftLessons, lesson.id, 1)), disabled: lessonIndex === draftLessons.length - 1, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white disabled:opacity-40" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-down" })), lesson.edit_url ? /* @__PURE__ */ React.createElement("a", { href: lesson.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Open lesson") : null) + )))); + }))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-publishing", eyebrow: "Publishing", title: "Placement and visibility", description: "Set the lesson metadata, schedule, and discovery fields before it goes live.", className: sectionClassName("lesson-publishing") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Difficulty", value: form.data.difficulty || "", onChange: (nextValue) => form.setData("difficulty", String(nextValue || "")), options: difficultyField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.difficulty })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Access", value: form.data.access_level || "", onChange: (nextValue) => form.setData("access_level", String(nextValue || "")), options: accessField?.options || [], searchable: false, className: "bg-black/20", error: form.errors.access_level }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "Lesson type", value: form.data.lesson_type, onChange: (event) => form.setData("lesson_type", event.target.value), error: form.errors.lesson_type, placeholder: "article, video, walkthrough" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "Microtags", value: form.data.tags, onChange: (event) => form.setData("tags", event.target.value), error: form.errors.tags, placeholder: "workflow, cleanup, publishing" }), /* @__PURE__ */ React.createElement("p", { className: "text-xs leading-6 text-slate-400" }, "Comma-separated short tags for the public lesson page and article discovery context."))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Category", value: form.data.category_id || "", onChange: (nextValue) => form.setData("category_id", String(nextValue || "")), options: categoryOptions, searchable: false, className: "bg-black/20", error: form.errors.category_id })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Publish at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.data.published_at || "", onChange: (nextValue) => form.setData("published_at", nextValue || ""), clearable: true, className: "bg-black/20" }), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.published_at }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(ToggleField$2, { label: "Featured", checked: Boolean(form.data.featured), onChange: (event) => form.setData("featured", event.target.checked), help: "Highlight this lesson in featured academy surfaces.", error: form.errors.featured }), /* @__PURE__ */ React.createElement(ToggleField$2, { label: "Active", checked: Boolean(form.data.active), onChange: (event) => form.setData("active", event.target.checked), help: "Keep inactive lessons hidden until the draft is ready.", error: form.errors.active }))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-seo", eyebrow: "SEO", title: "Search metadata", description: "Keep the lesson search-ready without stuffing the headline.", className: sectionClassName("lesson-seo") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$2, { label: "SEO title", value: form.data.seo_title, onChange: (event) => form.setData("seo_title", event.target.value), error: form.errors.seo_title, maxLength: 180, placeholder: "Optional search title" }), /* @__PURE__ */ React.createElement(TextField$2, { label: "Video URL", value: form.data.video_url, onChange: (event) => form.setData("video_url", event.target.value), error: form.errors.video_url, placeholder: "Optional lesson video URL" })), /* @__PURE__ */ React.createElement(TextAreaField$1, { label: "SEO description", value: form.data.seo_description, onChange: (event) => form.setData("seo_description", event.target.value), error: form.errors.seo_description, rows: 4, hint: "Keep this tighter than the excerpt and focused on search intent." })), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-categories", eyebrow: "Lesson categories", title: "Create category inline", description: "Add lesson categories without leaving the writing flow.", className: sectionClassName("lesson-categories"), actions: /* @__PURE__ */ React.createElement("a", { href: editorContext.categoryManageUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Manage all categories") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: categoryDraft.name, onChange: (event) => setCategoryDraft((current) => ({ ...current, name: event.target.value })), placeholder: "Category name", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: categoryDraft.slug, onChange: (event) => setCategoryDraft((current) => ({ ...current, slug: event.target.value })), placeholder: "Optional slug", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("textarea", { value: categoryDraft.description, onChange: (event) => setCategoryDraft((current) => ({ ...current, description: event.target.value })), rows: 3, placeholder: "Description", className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("input", { type: "number", value: categoryDraft.order_num, min: "0", onChange: (event) => setCategoryDraft((current) => ({ ...current, order_num: event.target.value })), className: "w-28 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "min-w-[260px] flex-1" }, /* @__PURE__ */ React.createElement(ToggleField$2, { label: "Category active", checked: categoryDraft.active, onChange: (event) => setCategoryDraft((current) => ({ ...current, active: event.target.checked })), help: "Inactive categories stay available for cleanup but disappear from regular lesson assignment." })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => void createCategory(), disabled: categorySaving, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100" }, categorySaving ? "Creating..." : "Create category")), categoryError ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, categoryError) : null), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, (categories || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-500" }, "No lesson categories yet.") : categories.map((category) => /* @__PURE__ */ React.createElement("div", { key: category.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, category.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.14em] text-slate-500" }, category.slug), category.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, category.description) : null), /* @__PURE__ */ React.createElement("a", { href: category.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Edit"))))))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-cover", eyebrow: "Cover image", title: "Hero asset", description: "Use drag and drop for the lesson image, or paste a direct URL when you already have one.", className: sectionClassName("lesson-cover") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement( WorldMediaUploadField, { label: "Lesson cover", @@ -16918,7 +20051,34 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de }, isTemporaryValue: Boolean(stagedCoverPath) && form.data.cover_image === stagedCoverPath } - ), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.cover_image }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Advanced cover path or URL"), /* @__PURE__ */ React.createElement("input", { value: form.data.cover_image, onChange: (event) => handleManualCoverChange(event.target.value), placeholder: "Optional external URL or stored object path", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "Keep this for migrations, imported lessons, or when you already know the exact asset path to use.")))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-preview", eyebrow: "Preview", title: "Lesson snapshot", description: "A quick view of what editors and visitors will scan first." }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/30" }, coverPreviewUrl ? /* @__PURE__ */ React.createElement("img", { src: coverPreviewUrl, alt: "Lesson cover preview", className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-56 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No cover image selected yet.")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Lesson summary"), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white" }, form.data.title || "Untitled lesson"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-400" }, form.data.excerpt || "Add a concise excerpt to frame the lesson before someone opens it."), /* @__PURE__ */ React.createElement("dl", { className: "mt-4 grid grid-cols-2 gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Difficulty"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.difficulty || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.access_level || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Reading"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.reading_minutes || "—", " min")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Body"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, bodyWordCount.toLocaleString(), " words"))))))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save lesson"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: deleteLesson, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null)), /* @__PURE__ */ React.createElement( + ), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.cover_image }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Advanced cover path or URL"), /* @__PURE__ */ React.createElement("input", { value: form.data.cover_image, onChange: (event) => handleManualCoverChange(event.target.value), placeholder: "Optional external URL or stored object path", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "Keep this for migrations, imported lessons, or when you already know the exact asset path to use.")))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-article-cover", eyebrow: "Article cover", title: "Inline article image", description: "This image is rendered just before the lesson content begins.", className: sectionClassName("lesson-article-cover") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement( + WorldMediaUploadField, + { + label: "Article cover", + slot: "cover", + value: form.data.article_cover_image, + previewUrl: articleCoverPreviewUrl, + emptyLabel: "Drop an article cover", + helperText: "Upload the image that appears above the lesson body. Use a strong wide image that still reads well inside the article column.", + uploadUrl: editorContext.coverUploadUrl, + deleteUrl: editorContext.coverDeleteUrl, + onChange: ({ path, url }) => { + setStagedArticleCoverPath(path || ""); + form.setData("article_cover_image", path || ""); + setArticleCoverPreviewUrl(url || ""); + }, + isTemporaryValue: Boolean(stagedArticleCoverPath) && form.data.article_cover_image === stagedArticleCoverPath + } + ), /* @__PURE__ */ React.createElement(FieldError$2, { message: form.errors.article_cover_image }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Advanced article cover path or URL"), /* @__PURE__ */ React.createElement("input", { value: form.data.article_cover_image, onChange: (event) => handleManualArticleCoverChange(event.target.value), placeholder: "Optional external URL or stored object path", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, "Use this when the article image already exists in storage or needs to point to an external source.")))), /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-revisions", eyebrow: "Safety net", title: "Revision history", description: "Each lesson update now saves the previous state first. Restore the full lesson or a single field when something goes wrong.", className: sectionClassName("lesson-revisions") }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/18 bg-sky-300/8 p-4 text-sm leading-6 text-slate-300" }, "Restoring from a revision creates another revision first, so you can undo the restore if needed."), revisions.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-black/20 px-4 py-5 text-sm leading-7 text-slate-400" }, "No revisions yet. The first saved update will capture the current lesson state.") : revisions.map((revision) => { + const selectedField = String(revisionFieldSelections[revision.id] || "content"); + return /* @__PURE__ */ React.createElement("div", { key: revision.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Revision #", revision.id), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, revision.created_label || "Recently saved", " by ", revision.actor_name || "Staff"), revision.change_note ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-5 text-slate-400" }, revision.change_note) : null), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => restoreLessonRevision(revision), className: "rounded-full border border-[#f39a24]/25 bg-[#f39a24]/12 px-3 py-1.5 text-xs font-semibold text-[#ffd5cd]" }, "Restore full lesson")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-[20px] border border-white/10 bg-white/[0.03] p-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, revision.snapshot?.title || "Untitled lesson snapshot"), revision.snapshot?.excerpt ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-5 text-slate-400" }, revision.snapshot.excerpt) : null, revision.snapshot?.content_preview ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs leading-5 text-slate-500" }, revision.snapshot.content_preview) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1" }, revision.snapshot?.course_count || 0, " courses"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1" }, revision.snapshot?.block_count || 0, " blocks"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Restore single field"), /* @__PURE__ */ React.createElement("select", { value: selectedField, onChange: (event) => setRevisionFieldSelections((current) => ({ ...current, [revision.id]: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }, LESSON_REVISION_FIELD_OPTIONS.map((option) => /* @__PURE__ */ React.createElement("option", { key: option.value, value: option.value }, option.label)))), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => restoreLessonRevision(revision, selectedField), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Restore selected field"))); + })))), showSupportRail ? /* @__PURE__ */ React.createElement("div", { className: "space-y-6 xl:sticky xl:top-6 xl:self-start" }, showWriteCompanion ? /* @__PURE__ */ React.createElement(SectionCard$5, { eyebrow: "Writing flow", title: "Author companion", description: "Keep the lesson opening tight, then expand through headings and examples. This panel stays compact so the editor remains the focus." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/18 bg-sky-300/8 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100/80" }, "Public path"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 break-all text-sm font-semibold text-white" }, lessonPathPreview), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, "Keep the headline specific enough that the slug reads clearly in search results and internal links.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Writing checklist"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 grid gap-2" }, lessonStatusItems.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.label, className: "flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", null, item.label), /* @__PURE__ */ React.createElement("span", { className: `rounded-full px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.14em] ${item.ready ? "border border-emerald-300/20 bg-emerald-300/10 text-emerald-100" : "border border-amber-300/20 bg-amber-300/10 text-amber-100"}` }, item.ready ? "Ready" : "Missing"))))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Article rhythm"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-3 text-sm leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "Lead with the problem in the first paragraph, then break the workflow into headline-sized steps."), /* @__PURE__ */ React.createElement("p", null, "Use Markdown import when you already have a draft, then switch back to the visual editor for structure, media, and cleanup."), /* @__PURE__ */ React.createElement("p", null, "Open full-height mode once the outline is stable so the body editor takes the entire screen."))))) : null, /* @__PURE__ */ React.createElement(SectionCard$5, { id: "lesson-preview", eyebrow: "Preview", title: "Lesson snapshot", description: "A quick view of what editors and visitors will scan first.", className: sectionClassName("lesson-preview") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/30" }, coverPreviewUrl ? /* @__PURE__ */ React.createElement("img", { src: coverPreviewUrl, alt: "Lesson cover preview", className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-56 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No hero cover image selected yet.")), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/30" }, articleCoverPreviewUrl ? /* @__PURE__ */ React.createElement("img", { src: articleCoverPreviewUrl, alt: "Lesson article cover preview", className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-56 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No article cover image selected yet."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Lesson summary"), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white" }, form.data.title || "Untitled lesson"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-400" }, form.data.excerpt || "Add a concise excerpt to frame the lesson before someone opens it."), /* @__PURE__ */ React.createElement("dl", { className: "mt-4 grid grid-cols-2 gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Difficulty"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.difficulty || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.access_level || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Reading"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.reading_minutes || "—", " min")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Body"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, bodyWordCount.toLocaleString(), " words")))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Article preview"), deferredArticlePreviewHtml ? /* @__PURE__ */ React.createElement( + "div", + { + className: "prose prose-invert mt-4 max-w-none prose-headings:text-white prose-p:text-slate-300 prose-p:leading-7 prose-li:text-slate-300 prose-strong:text-white prose-code:text-amber-300 prose-pre:border prose-pre:border-white/10 prose-pre:bg-slate-950/70 prose-blockquote:border-sky-300/30 prose-a:text-sky-300", + dangerouslySetInnerHTML: { __html: deferredArticlePreviewHtml } + } + ) : /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-500" }, "Add lesson body content to see the rendered article preview here.")))) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save lesson"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: deleteLesson, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null)), /* @__PURE__ */ React.createElement( JsonImportDialog$1, { open: jsonImportOpen, @@ -16936,9 +20096,27 @@ function LessonEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de }, onApply: applyJsonImport } + ), /* @__PURE__ */ React.createElement( + MarkdownImportDialog, + { + open: markdownImportOpen, + value: markdownImportValue, + error: markdownImportError, + onChange: (nextValue) => { + setMarkdownImportValue(nextValue); + if (markdownImportError) { + setMarkdownImportError(""); + } + }, + onClose: () => { + setMarkdownImportOpen(false); + setMarkdownImportError(""); + }, + onApply: applyMarkdownImport + } )); } -const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_12 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: LessonEditor }, Symbol.toStringTag, { value: "Module" })); @@ -16971,7 +20149,633 @@ function TextAreaField({ label, value, onChange, error, rows = 6, hint }) { return /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, label), /* @__PURE__ */ React.createElement("textarea", { value: value ?? "", onChange, rows, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm leading-7 text-white outline-none" }), hint ? /* @__PURE__ */ React.createElement("span", { className: "text-xs leading-5 text-slate-500" }, hint) : null, error ? /* @__PURE__ */ React.createElement("p", { className: "text-xs text-rose-300" }, error) : null); } function ToggleField$1({ label, checked, onChange, help, error }) { - return /* @__PURE__ */ React.createElement("label", { className: "flex items-start gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "mt-1" }), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "block font-semibold text-white" }, label), help ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-xs leading-5 text-slate-400" }, help) : null, error ? /* @__PURE__ */ React.createElement("span", { className: "mt-2 block text-xs text-rose-300" }, error) : null)); + return /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer items-start gap-4 rounded-[28px] border px-5 py-4 transition ${checked ? "border-[#f39a24]/35 bg-[#f39a24]/10" : "border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: Boolean(checked), onChange, className: "sr-only" }), /* @__PURE__ */ React.createElement("span", { className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border text-sm transition ${checked ? "border-[#f39a24] bg-[#f39a24] text-white" : "border-white/10 bg-[#151a29] text-transparent"}` }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-check" })), /* @__PURE__ */ React.createElement("span", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block text-base font-semibold tracking-[-0.02em] text-white" }, label), help ? /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-sm leading-6 text-slate-300" }, help) : null, error ? /* @__PURE__ */ React.createElement("span", { className: "mt-2 block text-xs text-rose-300" }, error) : null)); +} +const PROMPT_EDITOR_TABS = [ + { + id: "overview", + label: "Overview", + description: "Set the prompt identity, category, access level, and the short summary shown in the library.", + icon: "fa-compass-drafting", + sections: ["prompt-identity"] + }, + { + id: "prompt", + label: "Prompt Body", + description: "Write the main prompt, exclusions, usage notes, and workflow direction without crowding the rest of the form.", + icon: "fa-wand-magic-sparkles", + sections: ["prompt-body"] + }, + { + id: "comparisons", + label: "AI Model Comparisons", + description: "Compare how different AI models or providers behave on the same prompt so editors can keep the guidance reusable.", + icon: "fa-scale-balanced", + sections: ["prompt-comparisons"] + }, + { + id: "media", + label: "Media", + description: "Upload the preview image used across the prompt library and public prompt detail page.", + icon: "fa-image", + sections: ["prompt-media"] + }, + { + id: "publish", + label: "Publish", + description: "Control timing, SEO, and promotion state without showing every publishing option at once.", + icon: "fa-rocket-launch", + sections: ["prompt-publishing", "prompt-preview"] + } +]; +const PROMPT_FIELD_TAB_MAP = { + category_id: "overview", + title: "overview", + slug: "overview", + excerpt: "overview", + difficulty: "overview", + access_level: "overview", + aspect_ratio: "overview", + tags: "overview", + prompt: "prompt", + negative_prompt: "prompt", + usage_notes: "prompt", + workflow_notes: "prompt", + preview_image: "media", + preview_image_file: "media", + published_at: "publish", + seo_title: "publish", + seo_description: "publish", + featured: "publish", + prompt_of_week: "publish", + active: "publish" +}; +function slugifyPromptTitle(value) { + return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 180); +} +function stripPlainText(value) { + return String(value || "").replace(/\s+/g, " ").trim(); +} +function countPlainWords(value) { + const text2 = stripPlainText(value); + return text2 ? text2.split(/\s+/).length : 0; +} +function emptyPromptComparison() { + return { + client_key: `comparison-${Math.random().toString(36).slice(2, 10)}`, + provider: "", + model_name: "", + notes: "", + strengths: "", + weaknesses: "", + best_for: "", + image_path: "", + image_url: "", + thumb_path: "", + thumb_url: "", + settings: "", + score: "", + active: true + }; +} +function sanitizePromptComparison(value) { + if (!value || typeof value !== "object") { + return emptyPromptComparison(); + } + return { + client_key: String(value.client_key || emptyPromptComparison().client_key), + provider: String(value.provider || "").trim(), + model_name: String(value.model_name || "").trim(), + notes: String(value.notes || "").trim(), + strengths: String(value.strengths || "").trim(), + weaknesses: String(value.weaknesses || "").trim(), + best_for: String(value.best_for || "").trim(), + image_path: String(value.image_path || "").trim(), + image_url: String(value.image_url || "").trim(), + thumb_path: String(value.thumb_path || "").trim(), + thumb_url: String(value.thumb_url || "").trim(), + settings: String(value.settings || "").trim(), + score: value.score === "" || value.score === null || typeof value.score === "undefined" ? "" : String(value.score).trim(), + active: typeof value.active === "boolean" ? value.active : true + }; +} +function normalizePromptComparisons(value, { preserveEmpty = false } = {}) { + if (!Array.isArray(value)) return []; + return value.map((item) => { + if (typeof item === "string") { + const normalized2 = { ...emptyPromptComparison(), notes: item.trim() }; + return normalized2.notes || preserveEmpty ? normalized2 : null; + } + if (!item || typeof item !== "object") return preserveEmpty ? emptyPromptComparison() : null; + const normalized = sanitizePromptComparison(item); + const hasContent = [ + normalized.provider, + normalized.model_name, + normalized.notes, + normalized.strengths, + normalized.weaknesses, + normalized.best_for, + normalized.image_path, + normalized.thumb_path, + normalized.settings, + normalized.score + ].some(Boolean); + return hasContent || preserveEmpty ? normalized : null; + }).filter(Boolean); +} +function serializePromptComparisons(value) { + return normalizePromptComparisons(value).map((comparison) => ({ + provider: comparison.provider, + model_name: comparison.model_name, + notes: comparison.notes, + strengths: comparison.strengths, + weaknesses: comparison.weaknesses, + best_for: comparison.best_for, + image_path: comparison.image_path, + thumb_path: comparison.thumb_path, + settings: comparison.settings, + score: comparison.score === "" ? null : Number(comparison.score), + active: Boolean(comparison.active) + })); +} +function normalizeCodeList(values) { + return Array.from(new Set((Array.isArray(values) ? values : []).map((value) => String(value || "").trim()).filter(Boolean))); +} +function loadCodeList(storageKey2) { + if (typeof window === "undefined") return []; + try { + return normalizeCodeList(JSON.parse(window.localStorage.getItem(storageKey2) || "[]")); + } catch { + return []; + } +} +function saveCodeList(storageKey2, values) { + if (typeof window === "undefined") return; + window.localStorage.setItem(storageKey2, JSON.stringify(normalizeCodeList(values))); +} +function parsePromptImport(rawText, categoryOptions) { + let parsed; + try { + parsed = JSON.parse(String(rawText || "")); + } catch { + throw new Error("Could not parse JSON."); + } + if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") { + throw new Error("Import JSON must be an object."); + } + const next = {}; + const applied = []; + const apply = (key, value) => { + next[key] = value; + applied.push(key); + }; + if (parsed.title != null) apply("title", String(parsed.title)); + if (parsed.slug != null) apply("slug", String(parsed.slug)); + if (parsed.excerpt != null) apply("excerpt", String(parsed.excerpt)); + if (parsed.difficulty != null) apply("difficulty", String(parsed.difficulty)); + if (parsed.access_level != null) apply("access_level", String(parsed.access_level)); + if (parsed.access != null && parsed.access_level == null) apply("access_level", String(parsed.access)); + if (parsed.aspect_ratio != null) apply("aspect_ratio", String(parsed.aspect_ratio)); + if (parsed.tags != null) apply("tags", Array.isArray(parsed.tags) ? parsed.tags.map((tag) => typeof tag === "string" ? tag : tag?.name || tag?.label || tag?.title || tag?.slug || "").filter(Boolean).join(", ") : String(parsed.tags)); + if (parsed.prompt != null) apply("prompt", String(parsed.prompt)); + if (parsed.negative_prompt != null) apply("negative_prompt", String(parsed.negative_prompt)); + if (parsed.usage_notes != null) apply("usage_notes", String(parsed.usage_notes)); + if (parsed.workflow_notes != null) apply("workflow_notes", String(parsed.workflow_notes)); + if (parsed.preview_image != null) apply("preview_image", String(parsed.preview_image)); + if (parsed.preview_image_url != null && parsed.preview_image == null) apply("preview_image", String(parsed.preview_image_url)); + if (parsed.published_at != null) apply("published_at", String(parsed.published_at)); + if (parsed.seo_title != null) apply("seo_title", String(parsed.seo_title)); + if (parsed.seo_description != null) apply("seo_description", String(parsed.seo_description)); + if (parsed.featured != null) apply("featured", Boolean(parsed.featured)); + if (parsed.prompt_of_week != null) apply("prompt_of_week", Boolean(parsed.prompt_of_week)); + if (parsed.active != null) apply("active", Boolean(parsed.active)); + if (parsed.tool_notes != null || parsed.comparisons != null) { + const comparisonSource = Array.isArray(parsed.tool_notes) ? parsed.tool_notes : Array.isArray(parsed.comparisons) ? parsed.comparisons : []; + apply("tool_notes", normalizePromptComparisons(comparisonSource, { preserveEmpty: true })); + } + if (parsed.category_id != null || parsed.category_slug != null || parsed.category != null) { + const requested = String(parsed.category_id ?? parsed.category_slug ?? parsed.category).trim().toLowerCase(); + const match = (Array.isArray(categoryOptions) ? categoryOptions : []).find((option) => [option.id, option.value, option.slug, option.name, option.label].filter((candidate) => candidate != null).map((candidate) => String(candidate).trim().toLowerCase()).includes(requested)); + if (match) { + apply("category_id", String(match.id ?? match.value ?? "")); + } + } + if (applied.length === 0) { + throw new Error("The JSON did not contain any recognized prompt fields."); + } + return { next, applied }; +} +function getCsrfToken$g() { + if (typeof document === "undefined") return ""; + return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || ""; +} +async function uploadPromptComparisonMedia(uploadUrl, file) { + const formData = new FormData(); + formData.append("slot", "body"); + formData.append("image", file); + const response = await fetch(uploadUrl, { + method: "POST", + headers: { + "X-CSRF-TOKEN": getCsrfToken$g(), + Accept: "application/json" + }, + credentials: "same-origin", + body: formData + }); + const payload = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(payload?.message || "Could not upload comparison image right now."); + } + return payload; +} +async function deletePromptComparisonMedia(deleteUrl, path) { + if (!deleteUrl || !path) return; + await fetch(deleteUrl, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "X-CSRF-TOKEN": getCsrfToken$g(), + Accept: "application/json" + }, + credentials: "same-origin", + body: JSON.stringify({ path }) + }); +} +function CodeListEditor({ title, description, items, customItems, draftValue, setDraftValue, onAdd, onRemove }) { + return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-300" }, description), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, items.map((item) => { + const removable = customItems.includes(item); + return /* @__PURE__ */ React.createElement("span", { key: item, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, /* @__PURE__ */ React.createElement("span", null, item), removable ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onRemove(item), className: "text-slate-300 transition hover:text-rose-200" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-xmark" })) : null); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value), className: "min-w-[220px] flex-1 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-white outline-none", placeholder: `Add ${title.toLowerCase().slice(0, -1)}` }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onAdd, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100" }, "Add"))); +} +function PromptJsonImportDialog({ open, value, error, onChange, onClose, onApply }) { + const backdropRef = reactExports.useRef(null); + const [activeImportTab, setActiveImportTab] = reactExports.useState("input"); + const [copyFeedback, setCopyFeedback] = reactExports.useState(""); + const importTabs = [ + { id: "input", label: "Input", description: "Paste structured prompt JSON and apply it." }, + { id: "structure", label: "Structure example", description: "Reference payload for prompt templates." }, + { id: "docs", label: "Documentation", description: "Field rules and import notes." }, + { id: "prompts", label: "AI prompts", description: "Prompt templates to generate valid JSON." } + ]; + const structureExample = { + title: "Peaceful Fantasy Forest Wallpaper", + slug: "peaceful-fantasy-forest-wallpaper", + excerpt: "Create a calm fantasy forest wallpaper with glowing flowers, soft morning light, and gentle mist.", + category: "Academy", + difficulty: "beginner", + access_level: "free", + aspect_ratio: "16:9", + tags: ["wallpaper", "fantasy", "forest", "glowing flowers", "morning light"], + prompt: "Create a calm fantasy forest wallpaper with glowing flowers, soft morning light, gentle mist, and a peaceful magical atmosphere.", + negative_prompt: "blurry, muddy lighting, distorted tree trunks, low detail, oversaturated highlights", + usage_notes: "Start with the base prompt, then increase atmosphere and foliage density gradually.", + workflow_notes: "Good candidate for comparison across ChatGPT, Gemini, and Leonardo image models.", + preview_image: "https://files.skinbase.org/prompts/peaceful-fantasy-forest.webp", + featured: false, + prompt_of_week: false, + active: true, + tool_notes: [ + { + provider: "ChatGPT", + model_name: "4o Image", + settings: "High detail, cinematic natural light, 16:9", + notes: "Strong mood and color harmony, slightly idealized lighting.", + strengths: "Atmosphere, foliage glow, readable composition.", + weaknesses: "Can over-soften foreground detail.", + best_for: "Wallpaper-style fantasy environments.", + score: 9, + active: true + } + ] + }; + const promptJsonSchemaSummary = `You are generating a Skinbase Academy prompt template JSON object. + +Return only valid JSON. No markdown, no commentary, no code fences. + +Recommended fields: +- title: string +- slug: SEO-friendly slug +- excerpt: concise summary for cards and search results +- category_id or category/category_slug +- difficulty: beginner|intermediate|advanced|pro +- access_level: free|creator|pro|admin +- aspect_ratio: string like 1:1, 16:9, 3:2 +- tags: array of strings or objects with name/title/label/slug +- prompt: main prompt text +- negative_prompt: optional exclusions +- usage_notes: practical usage guidance +- workflow_notes: internal/editorial workflow notes +- preview_image: path or URL +- featured: boolean +- prompt_of_week: boolean +- active: boolean +- published_at: YYYY-MM-DD HH:MM:SS when known +- seo_title, seo_description +- tool_notes: array of model comparison objects + +tool_notes object fields: +- provider +- model_name +- settings +- notes +- strengths +- weaknesses +- best_for +- image_path or image_url when available +- score (1-10) +- active boolean + +Rules: +- Return one JSON object only. +- Keep excerpt concise and readable in cards. +- Keep tags relevant and production-usable. +- If you include tool_notes, keep them normalized and consistent.`; + const aiPromptExamples = [ + { + title: "Prompt template generator", + prompt: `${promptJsonSchemaSummary} + +Create a Skinbase Academy prompt template JSON object from the following creative brief. +- Keep the title concise and catalog-friendly. +- Write a prompt that is immediately usable. +- Write an excerpt that works in cards and search results. +- Add 5 to 12 focused tags. +- Include 2 to 4 tool_notes comparisons when the brief mentions multiple AI providers. + +Creative brief: +{{CREATIVE_BRIEF}}` + }, + { + title: "Provider comparison generator", + prompt: `${promptJsonSchemaSummary} + +Generate a prompt template JSON object for Skinbase Academy. +- Focus on the same core prompt being tested across multiple AI image providers. +- Include tool_notes entries for each provider. +- Each tool_notes item should explain settings, strengths, weaknesses, and best_for in plain production language. +- Return JSON only. + +Source notes: +{{MODEL_COMPARISON_NOTES}}` + }, + { + title: "Prompt migration import", + prompt: `${promptJsonSchemaSummary} + +Convert the following source prompt page into structured Skinbase Academy prompt JSON. +- Preserve the core instruction intent. +- Normalize tags and metadata. +- Convert provider reviews into tool_notes. +- Use category/category_slug when category_id is unknown. +- Return JSON only. + +Source content: +{{SOURCE_PROMPT_PAGE}}` + } + ]; + function tabButtonClass(active) { + return `flex-1 rounded-2xl border px-4 py-3 text-left transition ${active ? "border-sky-300/25 bg-sky-400/10 text-white" : "border-white/10 bg-white/[0.03] text-slate-400 hover:border-white/20 hover:bg-white/[0.05] hover:text-slate-200"}`; + } + const copyText = async (text2, label) => { + try { + await copyTextToClipboard(String(text2)); + setCopyFeedback(`${label} copied`); + window.setTimeout(() => setCopyFeedback(""), 1800); + } catch { + setCopyFeedback("Copy failed"); + window.setTimeout(() => setCopyFeedback(""), 1800); + } + }; + reactExports.useEffect(() => { + if (!open) return void 0; + const handleKeyDown2 = (event) => { + if (event.key === "Escape") { + onClose?.(); + } + }; + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); + }, [onClose, open]); + if (!open) return null; + return reactDomExports.createPortal( + /* @__PURE__ */ React.createElement( + "div", + { + ref: backdropRef, + className: "fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md", + onClick: (event) => { + if (event.target === backdropRef.current) { + onClose?.(); + } + }, + role: "presentation" + }, + /* @__PURE__ */ React.createElement("div", { role: "dialog", "aria-modal": "true", "aria-labelledby": "prompt-json-import-title", className: "flex h-[min(90vh,780px)] w-full max-w-5xl flex-col overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]" }, /* @__PURE__ */ React.createElement("div", { className: "border-b border-white/[0.06] bg-white/[0.02] px-6 py-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35" }, "Structured import"), /* @__PURE__ */ React.createElement("h3", { id: "prompt-json-import-title", className: "mt-2 text-lg font-semibold text-white" }, "Paste prompt JSON"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-white/65" }, "Use this to seed the prompt form from AI output, documentation drafts, or migrated prompt library content.")), /* @__PURE__ */ React.createElement("div", { className: "border-b border-white/[0.06] px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 md:grid-cols-4" }, importTabs.map((tab2) => /* @__PURE__ */ React.createElement("button", { key: tab2.id, type: "button", onClick: () => setActiveImportTab(tab2.id), className: tabButtonClass(activeImportTab === tab2.id) }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold" }, tab2.label), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs leading-5 text-current/70" }, tab2.description))))), /* @__PURE__ */ React.createElement("div", { className: "nova-scrollbar flex-1 min-h-0 overflow-y-auto px-6 py-5" }, activeImportTab === "input" ? /* @__PURE__ */ React.createElement("div", { className: "grid h-full min-h-0 gap-5 xl:grid-cols-[minmax(0,1.2fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement( + "textarea", + { + value, + onChange: (event) => onChange?.(event.target.value), + rows: 18, + placeholder: '{\n "title": "Peaceful Fantasy Forest Wallpaper",\n "excerpt": "Short summary...",\n "prompt": "Main prompt text...",\n "tool_notes": []\n}', + className: "nova-scrollbar w-full rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 font-mono text-sm text-white outline-none placeholder:text-white/30" + } + ), error ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100" }, error) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Recognized keys"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "title, slug, excerpt"), /* @__PURE__ */ React.createElement("p", null, "category_id, category, category_slug"), /* @__PURE__ */ React.createElement("p", null, "difficulty, access_level, aspect_ratio"), /* @__PURE__ */ React.createElement("p", null, "tags"), /* @__PURE__ */ React.createElement("p", null, "prompt, negative_prompt"), /* @__PURE__ */ React.createElement("p", null, "usage_notes, workflow_notes"), /* @__PURE__ */ React.createElement("p", null, "preview_image, preview_image_url"), /* @__PURE__ */ React.createElement("p", null, "published_at, seo_title, seo_description"), /* @__PURE__ */ React.createElement("p", null, "featured, prompt_of_week, active"), /* @__PURE__ */ React.createElement("p", null, "tool_notes, comparisons")))) : null, activeImportTab === "structure" ? /* @__PURE__ */ React.createElement("div", { className: "grid h-full min-h-0 gap-5 xl:grid-cols-[minmax(0,1.2fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "mb-3 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Structure example"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => copyText(JSON.stringify(structureExample, null, 2), "Structure example"), className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-white/[0.08]" }, "Copy example")), /* @__PURE__ */ React.createElement("pre", { className: "nova-scrollbar max-h-[52vh] overflow-auto rounded-[20px] border border-white/10 bg-slate-950/80 p-4 text-xs leading-6 text-slate-200" }, JSON.stringify(structureExample, null, 2))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Notes"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-3 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "`tool_notes` can be an array of comparison objects or a simpler array under `comparisons`."), /* @__PURE__ */ React.createElement("p", null, "`tags` can be strings or objects with `name`, `label`, `title`, or `slug`."), /* @__PURE__ */ React.createElement("p", null, "`preview_image` accepts either a stored path or an external URL.")))) : null, activeImportTab === "docs" ? /* @__PURE__ */ React.createElement("div", { className: "grid h-full min-h-0 gap-5 xl:grid-cols-[minmax(0,1.2fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Field guide"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-3 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "title"), " - public prompt name used in the library and detail page."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "excerpt"), " - short summary for cards, preview blocks, and search results."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "prompt"), " - the main instruction body shown to creators."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "negative_prompt"), " - exclusions, defects, or anti-patterns."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "tool_notes"), " - structured comparison notes for provider/model variants."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "preview_image"), " - existing asset URL or stored path. File upload still happens separately."), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", { className: "text-slate-200" }, "category_id"), " is preferred when known. `category` or `category_slug` are used for best-effort matching."))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Import rules"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "Unknown keys are ignored, so broader AI output is safe to paste."), /* @__PURE__ */ React.createElement("p", null, "Use JSON booleans for featured, prompt_of_week, and active."), /* @__PURE__ */ React.createElement("p", null, "Use `YYYY-MM-DD HH:MM:SS` for `published_at` when scheduling is needed."), /* @__PURE__ */ React.createElement("p", null, "Keep comparison rows normalized so provider/model names remain consistent in the frontend.")))) : null, activeImportTab === "prompts" ? /* @__PURE__ */ React.createElement("div", { className: "grid h-full min-h-0 gap-5 xl:grid-cols-[minmax(0,1.2fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, aiPromptExamples.map((example) => /* @__PURE__ */ React.createElement("div", { key: example.title, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-200/70" }, example.title), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => copyText(example.prompt, example.title), className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-white/[0.08]" }, "Copy prompt")), /* @__PURE__ */ React.createElement("pre", { className: "nova-scrollbar mt-3 max-h-56 overflow-auto whitespace-pre-wrap rounded-[18px] border border-white/10 bg-slate-950/80 p-4 text-sm leading-6 text-slate-200" }, example.prompt)))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Prompt tips"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2 leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "Tell the model to return JSON only, with no explanation text."), /* @__PURE__ */ React.createElement("p", null, "Ask for `tool_notes` when you want provider-by-provider comparison output."), /* @__PURE__ */ React.createElement("p", null, "Tell the model to keep titles and tags production-ready, not overly verbose.")))) : null), copyFeedback ? /* @__PURE__ */ React.createElement("div", { className: "px-6 pb-2 text-right text-xs font-medium text-sky-200/80" }, copyFeedback) : null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onClose?.(), className: "inline-flex items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/70 transition hover:bg-white/[0.08] hover:text-white" }, "Cancel"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => onApply?.(), className: "inline-flex items-center justify-center rounded-full border border-sky-300/25 bg-sky-400/90 px-4 py-2 text-sm font-semibold text-slate-950 transition hover:brightness-110" }, "Apply JSON"))) + ), + document.body + ); +} +function firstPromptErrorTab(errors) { + const firstKey = Object.keys(errors || {})[0]; + if (!firstKey) return null; + return PROMPT_FIELD_TAB_MAP[firstKey] || null; +} +function promptTabErrorCounts(errors) { + const counts = {}; + Object.keys(errors || {}).forEach((key) => { + const tabId = PROMPT_FIELD_TAB_MAP[key]; + if (!tabId) return; + counts[tabId] = Number(counts[tabId] || 0) + 1; + }); + return counts; +} +function PromptEditorTabs({ activeTab, onChange, errorCounts }) { + const activeMeta = PROMPT_EDITOR_TABS.find((tab2) => tab2.id === activeTab) || PROMPT_EDITOR_TABS[0]; + return /* @__PURE__ */ React.createElement("div", { className: "sticky top-4 z-20 rounded-[24px] border border-white/10 bg-[linear-gradient(180deg,rgba(7,11,18,0.92),rgba(5,8,14,0.88))] px-3 py-3 shadow-[0_18px_50px_rgba(2,6,23,0.18)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2", role: "tablist", "aria-label": "Prompt editor sections" }, PROMPT_EDITOR_TABS.map((tab2) => { + const isActive = tab2.id === activeTab; + const errorCount = Number(errorCounts?.[tab2.id] || 0); + return /* @__PURE__ */ React.createElement( + "button", + { + key: tab2.id, + type: "button", + role: "tab", + "aria-selected": isActive, + "aria-controls": `prompt-editor-panel-${tab2.id}`, + id: `prompt-editor-tab-${tab2.id}`, + onClick: () => onChange(tab2.id), + className: [ + "inline-flex items-center gap-2 rounded-2xl border px-4 py-2.5 text-sm font-semibold transition", + isActive ? "border-sky-300/25 bg-sky-300/12 text-sky-100 ring-1 ring-sky-300/20" : "border-white/10 bg-white/[0.03] text-white/80 hover:border-sky-300/30 hover:bg-sky-300/10 hover:text-white" + ].join(" ") + }, + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${tab2.icon} text-xs` }), + /* @__PURE__ */ React.createElement("span", null, tab2.label), + errorCount > 0 ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.14em] text-rose-100" }, errorCount) : null + ); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap items-center justify-between gap-3 px-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm leading-6 text-slate-400" }, activeMeta.description), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.16em] text-slate-500" }, activeMeta.sections.map((section) => /* @__PURE__ */ React.createElement("span", { key: section, className: "rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5" }, section.replace("prompt-", "").replace(/-/g, " ")))))); +} +function PromptComparisonEditor({ comparisons, setComparisons, editorContext }) { + const [busyIndex, setBusyIndex] = reactExports.useState(null); + const [uploadError, setUploadError] = reactExports.useState(""); + const [draftProvider, setDraftProvider] = reactExports.useState(""); + const [draftModel, setDraftModel] = reactExports.useState(""); + const [customProviders, setCustomProviders] = reactExports.useState([]); + const [customModels, setCustomModels] = reactExports.useState([]); + const providerStorageKey = "academy.prompt-comparison.providers"; + const modelStorageKey = "academy.prompt-comparison.models"; + const defaultProviders = normalizeCodeList(editorContext?.comparisonCodeLists?.providers || []); + const defaultModels = normalizeCodeList(editorContext?.comparisonCodeLists?.models || []); + const providerOptions = normalizeCodeList([...defaultProviders, ...customProviders, ...comparisons.map((comparison) => comparison.provider)]).map((value) => ({ value, label: value })); + const modelOptions = normalizeCodeList([...defaultModels, ...customModels, ...comparisons.map((comparison) => comparison.model_name)]).map((value) => ({ value, label: value })); + reactExports.useEffect(() => { + setCustomProviders(loadCodeList(providerStorageKey)); + setCustomModels(loadCodeList(modelStorageKey)); + }, []); + const replaceComparison = (index2, nextComparison) => { + setComparisons(comparisons.map((comparison, currentIndex) => currentIndex === index2 ? sanitizePromptComparison(nextComparison) : comparison)); + }; + const updateComparison = (index2, field, value) => { + replaceComparison(index2, { ...comparisons[index2], [field]: value }); + }; + const removeStoredMedia = async (comparison) => { + const deleteUrl = editorContext?.comparisonMediaDeleteUrl || ""; + const imagePaths = [comparison?.image_path, comparison?.thumb_path].filter(Boolean); + await Promise.all(imagePaths.map((path) => deletePromptComparisonMedia(deleteUrl, path))); + }; + const removeComparison = async (index2) => { + const comparison = comparisons[index2]; + setComparisons(comparisons.filter((_2, currentIndex) => currentIndex !== index2)); + try { + await removeStoredMedia(comparison); + } catch { + } + }; + const moveComparison = (index2, direction) => { + const nextIndex = index2 + direction; + if (nextIndex < 0 || nextIndex >= comparisons.length) return; + const nextComparisons = [...comparisons]; + const [entry] = nextComparisons.splice(index2, 1); + nextComparisons.splice(nextIndex, 0, entry); + setComparisons(nextComparisons); + }; + const resolvePreviewUrl = (comparison) => comparison.image_url || comparison.thumb_url || ""; + const addCustomProvider = () => { + const nextValue = String(draftProvider || "").trim(); + if (!nextValue) return; + const nextItems = normalizeCodeList([...customProviders, nextValue]); + setCustomProviders(nextItems); + saveCodeList(providerStorageKey, nextItems); + setDraftProvider(""); + }; + const addCustomModel = () => { + const nextValue = String(draftModel || "").trim(); + if (!nextValue) return; + const nextItems = normalizeCodeList([...customModels, nextValue]); + setCustomModels(nextItems); + saveCodeList(modelStorageKey, nextItems); + setDraftModel(""); + }; + const removeCustomProvider = (value) => { + const nextItems = customProviders.filter((item) => item !== value); + setCustomProviders(nextItems); + saveCodeList(providerStorageKey, nextItems); + }; + const removeCustomModel = (value) => { + const nextItems = customModels.filter((item) => item !== value); + setCustomModels(nextItems); + saveCodeList(modelStorageKey, nextItems); + }; + const handleUpload = async (index2, file) => { + const uploadUrl = editorContext?.comparisonMediaUploadUrl || ""; + if (!uploadUrl || !file) return; + setBusyIndex(index2); + setUploadError(""); + const previous2 = comparisons[index2]; + try { + const uploaded = await uploadPromptComparisonMedia(uploadUrl, file); + replaceComparison(index2, { + ...previous2, + image_path: uploaded.path || "", + image_url: uploaded.url || "", + thumb_path: previous2?.thumb_path || "", + thumb_url: previous2?.thumb_url || "" + }); + if (previous2?.image_path && previous2.image_path !== uploaded.path) { + await deletePromptComparisonMedia(editorContext?.comparisonMediaDeleteUrl || "", previous2.image_path); + } + if (previous2?.thumb_path && previous2.thumb_path !== uploaded.path) { + await deletePromptComparisonMedia(editorContext?.comparisonMediaDeleteUrl || "", previous2.thumb_path); + } + } catch (error) { + setUploadError(error instanceof Error ? error.message : "Could not upload comparison image."); + } finally { + setBusyIndex(null); + } + }; + const clearMedia = async (index2) => { + const comparison = comparisons[index2]; + replaceComparison(index2, { + ...comparison, + image_path: "", + image_url: "", + thumb_path: "", + thumb_url: "" + }); + try { + await removeStoredMedia(comparison); + } catch { + } + }; + return /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Structured blocks"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-300" }, "Upload the generated output for each provider, then document what it does well, where it fails, and which workflow it fits best.")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setComparisons([...comparisons, emptyPromptComparison()]), className: "rounded-full border border-amber-300/25 bg-amber-300/12 px-4 py-2.5 text-sm font-semibold text-amber-100" }, "+ Add AI Comparison")), uploadError ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[20px] border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, uploadError) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement( + CodeListEditor, + { + title: "Providers", + description: "Use a short consistent provider name in the dropdown. Add custom ones when a new tool appears.", + items: providerOptions.map((option) => option.value), + customItems: customProviders, + draftValue: draftProvider, + setDraftValue: setDraftProvider, + onAdd: addCustomProvider, + onRemove: removeCustomProvider + } + ), /* @__PURE__ */ React.createElement( + CodeListEditor, + { + title: "Models", + description: "Keep model names standardized so frontend comparisons stay readable and sortable.", + items: modelOptions.map((option) => option.value), + customItems: customModels, + draftValue: draftModel, + setDraftValue: setDraftModel, + onAdd: addCustomModel, + onRemove: removeCustomModel + } + )), comparisons.length ? comparisons.map((comparison, index2) => /* @__PURE__ */ React.createElement("section", { key: comparison.client_key || `comparison-${index2}`, className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-200/75" }, "AI model comparison"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-lg font-semibold tracking-[-0.03em] text-white" }, comparison.model_name || `Comparison ${String(index2 + 1).padStart(2, "0")}`), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Document how this model handles the same prompt so creators can choose the right tool faster.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => moveComparison(index2, -1), disabled: index2 === 0, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => moveComparison(index2, 1), disabled: index2 === comparisons.length - 1, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white disabled:opacity-40" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-down" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeComparison(index2), className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-3 py-2 text-xs font-semibold text-rose-100" }, "Remove"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 lg:grid-cols-[220px_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[22px] border border-white/10 bg-slate-950" }, resolvePreviewUrl(comparison) ? /* @__PURE__ */ React.createElement("img", { src: resolvePreviewUrl(comparison), alt: comparison.model_name || comparison.provider || `Comparison ${index2 + 1}`, className: "h-40 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-40 items-center justify-center px-4 text-center text-sm text-slate-500" }, "Upload generated output from this provider")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement("label", { className: "cursor-pointer rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-center text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18" }, /* @__PURE__ */ React.createElement( + "input", + { + type: "file", + accept: "image/jpeg,image/png,image/webp", + className: "hidden", + disabled: busyIndex === index2, + onChange: (event) => { + const file = event.target.files?.[0] || null; + if (file) { + handleUpload(index2, file); + } + event.target.value = ""; + } + } + ), busyIndex === index2 ? "Uploading..." : resolvePreviewUrl(comparison) ? "Replace image" : "Upload image"), comparison.image_path ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearMedia(index2), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-slate-200 transition hover:bg-white/[0.08]" }, "Clear image") : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[20px] border border-white/10 bg-black/30 px-4 py-3 text-xs leading-6 text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, "Stored asset"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 break-all" }, comparison.image_path || "No uploaded comparison image yet."))), /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "mt-0 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(NovaSelect, { label: "Provider", value: comparison.provider || "", onChange: (nextValue) => updateComparison(index2, "provider", String(nextValue || "")), options: providerOptions, searchable: true, className: "rounded-2xl bg-black/20" }), /* @__PURE__ */ React.createElement(NovaSelect, { label: "Model", value: comparison.model_name || "", onChange: (nextValue) => updateComparison(index2, "model_name", String(nextValue || "")), options: modelOptions, searchable: true, className: "rounded-2xl bg-black/20" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextAreaField, { label: "Generation details", value: comparison.settings, onChange: (event) => updateComparison(index2, "settings", event.target.value), rows: 4, hint: "Mention where it was generated, model mode, aspect ratio, or special settings." }), /* @__PURE__ */ React.createElement(TextAreaField, { label: "Notes", value: comparison.notes, onChange: (event) => updateComparison(index2, "notes", event.target.value), rows: 4, hint: "How does this provider interpret the prompt overall?" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_140px_180px]" }, /* @__PURE__ */ React.createElement(TextAreaField, { label: "Best for", value: comparison.best_for, onChange: (event) => updateComparison(index2, "best_for", event.target.value), rows: 4, hint: "What type of creator or output is this model the best fit for?" }), /* @__PURE__ */ React.createElement(TextField$1, { label: "Score", type: "number", min: "1", max: "10", value: comparison.score, onChange: (event) => updateComparison(index2, "score", event.target.value), placeholder: "1-10" }), /* @__PURE__ */ React.createElement(ToggleField$1, { label: "Visible on frontend", checked: Boolean(comparison.active), onChange: (event) => updateComparison(index2, "active", event.target.checked), help: "Turn this off to keep the comparison saved but hidden publicly." })))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextAreaField, { label: "Strengths", value: comparison.strengths, onChange: (event) => updateComparison(index2, "strengths", event.target.value), rows: 4, hint: "What this model consistently does well with the prompt." }), /* @__PURE__ */ React.createElement(TextAreaField, { label: "Weaknesses", value: comparison.weaknesses, onChange: (event) => updateComparison(index2, "weaknesses", event.target.value), rows: 4, hint: "What tends to fail or need correction in post-processing." })))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/10 bg-black/20 px-6 py-8 text-sm text-slate-400" }, "No comparison blocks yet. Add one when the same prompt needs model-specific guidance.")); } function Field$4({ field, form }) { const value = form.data[field.name]; @@ -17149,8 +20953,8 @@ function PromptPreviewDropzone({ form, previewUrl }) { ) ); } -function PromptEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method }) { - const form = G({ ...record, preview_image_file: null }); +function PromptEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method, editorContext }) { + const form = G$1({ ...record, new_category_name: "", preview_image_file: null, tool_notes: normalizePromptComparisons(record.tool_notes, { preserveEmpty: true }) }); const categoryField = reactExports.useMemo(() => getField(fields, "category_id"), [fields]); const difficultyField = reactExports.useMemo(() => getField(fields, "difficulty"), [fields]); const accessField = reactExports.useMemo(() => getField(fields, "access_level"), [fields]); @@ -17159,10 +20963,52 @@ function PromptEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de const promptOfWeekField = reactExports.useMemo(() => getField(fields, "prompt_of_week"), [fields]); const activeField = reactExports.useMemo(() => getField(fields, "active"), [fields]); const seoDescriptionField = reactExports.useMemo(() => getField(fields, "seo_description"), [fields]); + const slugTouchedRef = reactExports.useRef(Boolean(String(record.slug || "").trim())); + const [activeTab, setActiveTab] = reactExports.useState("overview"); + const [jsonImportOpen, setJsonImportOpen] = reactExports.useState(false); + const [jsonImportValue, setJsonImportValue] = reactExports.useState(""); + const [jsonImportError, setJsonImportError] = reactExports.useState(""); const previewUrl = form.data.preview_image_url || ""; + const tagCount = String(form.data.tags || "").split(/[,\n]/).map((item) => item.trim()).filter(Boolean).length; + const promptWordCount = reactExports.useMemo(() => countPlainWords(form.data.prompt), [form.data.prompt]); + const negativePromptWordCount = reactExports.useMemo(() => countPlainWords(form.data.negative_prompt), [form.data.negative_prompt]); + const comparisonCount = Array.isArray(form.data.tool_notes) ? form.data.tool_notes.length : 0; + const tabErrorCounts = reactExports.useMemo(() => promptTabErrorCounts(form.errors), [form.errors]); + const activeTabMeta = reactExports.useMemo(() => PROMPT_EDITOR_TABS.find((tab2) => tab2.id === activeTab) || PROMPT_EDITOR_TABS[0], [activeTab]); + const visibleSections = reactExports.useMemo(() => new Set(activeTabMeta.sections), [activeTabMeta]); + const sectionClassName = (sectionId, className = "") => `${visibleSections.has(sectionId) ? "" : "hidden"} ${className}`.trim(); + const editorLinks = editorContext?.links || {}; + reactExports.useEffect(() => { + if (slugTouchedRef.current) return; + form.setData("slug", slugifyPromptTitle(form.data.title)); + }, [form, form.data.title]); + reactExports.useEffect(() => { + const nextTab = firstPromptErrorTab(form.errors); + if (!nextTab) return; + setActiveTab(nextTab); + }, [form.errors]); + const applyJsonImport = () => { + try { + const categoryOptions = Array.isArray(categoryField?.options) ? categoryField.options : []; + const parsed = parsePromptImport(jsonImportValue, categoryOptions); + Object.entries(parsed.next).forEach(([key, value]) => { + form.setData(key, value); + }); + if (parsed.next.slug != null) { + slugTouchedRef.current = true; + } + setJsonImportError(""); + setJsonImportOpen(false); + } catch (error) { + setJsonImportError(error instanceof Error ? error.message : "Could not parse JSON."); + } + }; const submit = (event) => { event.preventDefault(); - const payload = normalizePayload(fields, form.data); + const payload = normalizePayload(fields, { + ...form.data, + tool_notes: serializePromptComparisons(form.data.tool_notes) + }); form.transform(() => payload); if (method === "patch") { form.patch(submitUrl); @@ -17170,56 +21016,35 @@ function PromptEditor({ title, subtitle, fields, record, submitUrl, indexUrl, de } form.post(submitUrl); }; - const tagCount = String(form.data.tags || "").split(/[,\n]/).map((item) => item.trim()).filter(Boolean).length; - return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se, { title: `Admin · ${title}` }), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,340px)]" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6" }, /* @__PURE__ */ React.createElement( - SectionCard$4, - { - eyebrow: "Identity", - title: "Core prompt details", - description: "Set the catalog identity first so the prompt is easy to find, sort, and preview." - }, - /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, categoryField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: categoryField.label, value: form.data.category_id ?? "", onChange: (nextValue) => form.setData("category_id", nextValue ?? ""), options: categoryField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.category_id }) : null, difficultyField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: difficultyField.label, value: form.data.difficulty ?? "", onChange: (nextValue) => form.setData("difficulty", nextValue ?? ""), options: difficultyField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.difficulty }) : null), - /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, accessField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: accessField.label, value: form.data.access_level ?? "", onChange: (nextValue) => form.setData("access_level", nextValue ?? ""), options: accessField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.access_level }) : null, /* @__PURE__ */ React.createElement(TextField$1, { label: "Aspect ratio", value: form.data.aspect_ratio || "", onChange: (event) => form.setData("aspect_ratio", event.target.value), error: form.errors.aspect_ratio, placeholder: "1:1, 16:9, 3:2" })), - /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$1, { label: "Title", value: form.data.title || "", onChange: (event) => form.setData("title", event.target.value), error: form.errors.title, maxLength: 180 }), /* @__PURE__ */ React.createElement(TextField$1, { label: "Slug", value: form.data.slug || "", onChange: (event) => form.setData("slug", event.target.value), error: form.errors.slug, maxLength: 180, placeholder: "prompt-template-slug" })), - /* @__PURE__ */ React.createElement(TextAreaField, { label: "Excerpt", value: form.data.excerpt || "", onChange: (event) => form.setData("excerpt", event.target.value), error: form.errors.excerpt, rows: 4, hint: "Short summary shown in the library and preview cards." }), - /* @__PURE__ */ React.createElement(TextField$1, { label: "Tags", value: form.data.tags || "", onChange: (event) => form.setData("tags", event.target.value), error: form.errors.tags, placeholder: "wallpaper, cinematic, neon, portrait" }) - ), /* @__PURE__ */ React.createElement( - SectionCard$4, - { - eyebrow: "Prompt body", - title: "Prompt instructions", - description: "Write the instruction stack, guardrails, and production notes in a way that is easy to scan." - }, - /* @__PURE__ */ React.createElement(TextAreaField, { label: "Prompt", value: form.data.prompt || "", onChange: (event) => form.setData("prompt", event.target.value), error: form.errors.prompt, rows: 10, hint: "This is the main model instruction used by creators." }), - /* @__PURE__ */ React.createElement(TextAreaField, { label: "Negative prompt", value: form.data.negative_prompt || "", onChange: (event) => form.setData("negative_prompt", event.target.value), error: form.errors.negative_prompt, rows: 5, hint: "Optional exclusions, artifacts, or anti-patterns to avoid." }), - /* @__PURE__ */ React.createElement(TextAreaField, { label: "Usage notes", value: form.data.usage_notes || "", onChange: (event) => form.setData("usage_notes", event.target.value), error: form.errors.usage_notes, rows: 5, hint: "Explain how to apply the prompt in a practical workflow." }), - /* @__PURE__ */ React.createElement(TextAreaField, { label: "Workflow notes", value: form.data.workflow_notes || "", onChange: (event) => form.setData("workflow_notes", event.target.value), error: form.errors.workflow_notes, rows: 5, hint: "Internal editorial notes, camera settings, or prompt variants." }) - ), /* @__PURE__ */ React.createElement( - SectionCard$4, - { - eyebrow: "Publishing", - title: "Release controls", - description: "Choose when the prompt becomes visible and how it behaves in the academy." - }, - /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, publishedAtField ? /* @__PURE__ */ React.createElement(DateTimePicker, { label: publishedAtField.label, value: form.data.published_at || "", onChange: (nextValue) => form.setData("published_at", nextValue || ""), error: form.errors.published_at, clearable: true, className: "bg-black/20" }) : null, /* @__PURE__ */ React.createElement(TextField$1, { label: "SEO title", value: form.data.seo_title || "", onChange: (event) => form.setData("seo_title", event.target.value), error: form.errors.seo_title, maxLength: 180 })), - seoDescriptionField ? /* @__PURE__ */ React.createElement(TextAreaField, { label: seoDescriptionField.label, value: form.data.seo_description || "", onChange: (event) => form.setData("seo_description", event.target.value), error: form.errors.seo_description, rows: 4 }) : null, - /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-3" }, featuredField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: featuredField.label, checked: Boolean(form.data.featured), onChange: (event) => form.setData("featured", event.target.checked), help: "Highlight this prompt in featured rails.", error: form.errors.featured }) : null, promptOfWeekField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: promptOfWeekField.label, checked: Boolean(form.data.prompt_of_week), onChange: (event) => form.setData("prompt_of_week", event.target.checked), help: "Promote this prompt as the current weekly pick.", error: form.errors.prompt_of_week }) : null, activeField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: activeField.label, checked: Boolean(form.data.active), onChange: (event) => form.setData("active", event.target.checked), help: "Keep draft prompts hidden until they are ready.", error: form.errors.active }) : null) - )), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6 xl:sticky xl:top-6 xl:self-start" }, /* @__PURE__ */ React.createElement( - SectionCard$4, - { - eyebrow: "At a glance", - title: "Prompt preview", - description: "A compact summary of what editors and visitors will see." - }, - /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/30" }, previewUrl || form.data.preview_image ? /* @__PURE__ */ React.createElement("img", { src: previewUrl || form.data.preview_image, alt: "Prompt preview", className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-56 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No preview image selected yet.")), - /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Prompt summary"), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white" }, form.data.title || "Untitled prompt"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-400" }, form.data.excerpt || "Add a concise excerpt to give the prompt some context in the library."), /* @__PURE__ */ React.createElement("dl", { className: "mt-4 grid grid-cols-2 gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Difficulty"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.difficulty || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.access_level || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Aspect"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.aspect_ratio || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Tags"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, tagCount))), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs leading-6 text-slate-500" }, "Uploaded images are converted to WebP and stored on the Contabo S3-backed CDN before the record is saved.")) - ), /* @__PURE__ */ React.createElement(PromptPreviewDropzone, { form, previewUrl }))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save prompt"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${title}` }), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-6 pb-16" }, editorLinks.preview ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: editorLinks.preview, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Preview public page")) : null, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_34%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.94))] shadow-[0_24px_70px_rgba(2,6,23,0.34)] backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-white/10 px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white transition hover:bg-white/[0.08]" }, "Back to prompts"), /* @__PURE__ */ React.createElement("span", null, destroyUrl ? "Edit prompt" : "New prompt")), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.05em] text-white" }, form.data.title || "Untitled academy prompt"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-7 text-slate-300" }, "Keep the prompt editor focused like a production worksheet: identity first, then the actual prompt body, then model comparisons and publishing details in separate tabs.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setJsonImportOpen(true), className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Import JSON"), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-2xl border border-sky-300/25 bg-sky-300/12 px-4 py-2.5 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save prompt")))), /* @__PURE__ */ React.createElement(PromptEditorTabs, { activeTab, onChange: setActiveTab, errorCounts: tabErrorCounts }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.14)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Current workspace"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, activeTabMeta.label), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-6 text-slate-400" }, activeTabMeta.description)), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Prompt words"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, promptWordCount.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Negative words"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, negativePromptWordCount.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Tags"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, tagCount)), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Comparisons"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, comparisonCount))))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6", role: "tabpanel", id: `prompt-editor-panel-${activeTab}`, "aria-labelledby": `prompt-editor-tab-${activeTab}` }, /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "Identity", title: "Core prompt details", description: "Set the catalog identity first so the prompt is easy to find, sort, and preview.", className: sectionClassName("prompt-identity") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, categoryField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: categoryField.label, value: form.data.category_id ?? "", onChange: (nextValue) => { + form.setData("category_id", nextValue ?? ""); + if (nextValue) { + form.setData("new_category_name", ""); + } + }, options: categoryField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.category_id }) : null, /* @__PURE__ */ React.createElement(TextField$1, { label: "Or enter new category", value: form.data.new_category_name || "", onChange: (event) => form.setData("new_category_name", event.target.value), error: form.errors.new_category_name, placeholder: "New prompt category name" }), difficultyField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: difficultyField.label, value: form.data.difficulty ?? "", onChange: (nextValue) => form.setData("difficulty", nextValue ?? ""), options: difficultyField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.difficulty }) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-3 text-xs leading-6 text-slate-400" }, "Choose an existing category from the dropdown or type a new category name. When you save, a new prompt category will be created automatically and attached to this prompt."), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, accessField ? /* @__PURE__ */ React.createElement(NovaSelect, { label: accessField.label, value: form.data.access_level ?? "", onChange: (nextValue) => form.setData("access_level", nextValue ?? ""), options: accessField.options || [], searchable: false, className: "rounded-2xl bg-black/20", error: form.errors.access_level }) : null, /* @__PURE__ */ React.createElement(TextField$1, { label: "Aspect ratio", value: form.data.aspect_ratio || "", onChange: (event) => form.setData("aspect_ratio", event.target.value), error: form.errors.aspect_ratio, placeholder: "1:1, 16:9, 3:2" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField$1, { label: "Title", value: form.data.title || "", onChange: (event) => form.setData("title", event.target.value), error: form.errors.title, maxLength: 180 }), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + slugTouchedRef.current = false; + form.setData("slug", slugifyPromptTitle(form.data.title)); + }, className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold text-white" }, "Sync")), /* @__PURE__ */ React.createElement("input", { value: form.data.slug || "", onChange: (event) => { + slugTouchedRef.current = String(event.target.value).trim() !== ""; + form.setData("slug", event.target.value); + }, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none", maxLength: 180, placeholder: "prompt-template-slug" }), form.errors.slug ? /* @__PURE__ */ React.createElement("p", { className: "text-xs text-rose-300" }, form.errors.slug) : null)), /* @__PURE__ */ React.createElement(TextAreaField, { label: "Excerpt", value: form.data.excerpt || "", onChange: (event) => form.setData("excerpt", event.target.value), error: form.errors.excerpt, rows: 4, hint: "Short summary shown in the library and preview cards." }), /* @__PURE__ */ React.createElement(TextField$1, { label: "Tags", value: form.data.tags || "", onChange: (event) => form.setData("tags", event.target.value), error: form.errors.tags, placeholder: "wallpaper, cinematic, neon, portrait" })), /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "Prompt body", title: "Prompt instructions", description: "Write the instruction stack, guardrails, and workflow notes without cramming publishing settings into the same view.", className: sectionClassName("prompt-body") }, /* @__PURE__ */ React.createElement(TextAreaField, { label: "Prompt", value: form.data.prompt || "", onChange: (event) => form.setData("prompt", event.target.value), error: form.errors.prompt, rows: 12, hint: "This is the main model instruction used by creators." }), /* @__PURE__ */ React.createElement(TextAreaField, { label: "Negative prompt", value: form.data.negative_prompt || "", onChange: (event) => form.setData("negative_prompt", event.target.value), error: form.errors.negative_prompt, rows: 6, hint: "Optional exclusions, artifacts, or anti-patterns to avoid." }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextAreaField, { label: "Usage notes", value: form.data.usage_notes || "", onChange: (event) => form.setData("usage_notes", event.target.value), error: form.errors.usage_notes, rows: 6, hint: "Explain how to apply the prompt in a practical workflow." }), /* @__PURE__ */ React.createElement(TextAreaField, { label: "Workflow notes", value: form.data.workflow_notes || "", onChange: (event) => form.setData("workflow_notes", event.target.value), error: form.errors.workflow_notes, rows: 6, hint: "Internal editorial notes, camera settings, or prompt variants." }))), /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "Structured blocks", title: "AI model comparisons", description: "Add reusable same-prompt comparison notes without burying provider-specific behavior inside the main prompt body.", className: sectionClassName("prompt-comparisons") }, /* @__PURE__ */ React.createElement(PromptComparisonEditor, { comparisons: Array.isArray(form.data.tool_notes) ? form.data.tool_notes : [], setComparisons: (nextValue) => form.setData("tool_notes", normalizePromptComparisons(nextValue, { preserveEmpty: true })), editorContext })), /* @__PURE__ */ React.createElement("div", { className: sectionClassName("prompt-media") }, /* @__PURE__ */ React.createElement(PromptPreviewDropzone, { form, previewUrl })), /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "Publishing", title: "Release controls", description: "Choose when the prompt becomes visible and how it behaves in the academy.", className: sectionClassName("prompt-publishing") }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, publishedAtField ? /* @__PURE__ */ React.createElement(DateTimePicker, { label: publishedAtField.label, value: form.data.published_at || "", onChange: (nextValue) => form.setData("published_at", nextValue || ""), error: form.errors.published_at, clearable: true, className: "bg-black/20" }) : null, /* @__PURE__ */ React.createElement(TextField$1, { label: "SEO title", value: form.data.seo_title || "", onChange: (event) => form.setData("seo_title", event.target.value), error: form.errors.seo_title, maxLength: 180 })), seoDescriptionField ? /* @__PURE__ */ React.createElement(TextAreaField, { label: seoDescriptionField.label, value: form.data.seo_description || "", onChange: (event) => form.setData("seo_description", event.target.value), error: form.errors.seo_description, rows: 4 }) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-3" }, featuredField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: featuredField.label, checked: Boolean(form.data.featured), onChange: (event) => form.setData("featured", event.target.checked), help: "Highlight this prompt in featured rails.", error: form.errors.featured }) : null, promptOfWeekField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: promptOfWeekField.label, checked: Boolean(form.data.prompt_of_week), onChange: (event) => form.setData("prompt_of_week", event.target.checked), help: "Promote this prompt as the current weekly pick.", error: form.errors.prompt_of_week }) : null, activeField ? /* @__PURE__ */ React.createElement(ToggleField$1, { label: activeField.label, checked: Boolean(form.data.active), onChange: (event) => form.setData("active", event.target.checked), help: "Keep draft prompts hidden until they are ready.", error: form.errors.active }) : null)), /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "Preview", title: "Public-facing snapshot", description: "Check the prompt card summary, tags, and current image before publishing.", className: sectionClassName("prompt-preview") }, /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/30" }, previewUrl || form.data.preview_image ? /* @__PURE__ */ React.createElement("img", { src: previewUrl || form.data.preview_image, alt: "Prompt preview", className: "h-64 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-64 items-center justify-center px-6 text-center text-sm text-slate-500" }, "No preview image selected yet.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, "Prompt summary"), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-2xl font-semibold tracking-[-0.04em] text-white" }, form.data.title || "Untitled prompt"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-400" }, form.data.excerpt || "Add a concise excerpt to give the prompt some context in the library."), /* @__PURE__ */ React.createElement("dl", { className: "mt-4 grid grid-cols-2 gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Difficulty"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.difficulty || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.access_level || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Aspect"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, form.data.aspect_ratio || "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2" }, /* @__PURE__ */ React.createElement("dt", { className: "uppercase tracking-[0.16em] text-slate-500" }, "Comparisons"), /* @__PURE__ */ React.createElement("dd", { className: "mt-1 text-sm text-white" }, comparisonCount)))))), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 space-y-6 xl:sticky xl:top-6 xl:self-start" }, /* @__PURE__ */ React.createElement(SectionCard$4, { eyebrow: "At a glance", title: "Prompt status", description: "A compact summary while you work through the tabs." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-1" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Prompt words"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-lg font-semibold text-white" }, promptWordCount.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Tags"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-lg font-semibold text-white" }, tagCount)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Comparisons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-lg font-semibold text-white" }, comparisonCount)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Visibility"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-lg font-semibold text-white" }, form.data.active ? "Active" : "Draft"))), /* @__PURE__ */ React.createElement("p", { className: "text-xs leading-6 text-slate-500" }, "Uploaded images are converted to WebP and stored on the Contabo S3-backed CDN before the record is saved.")))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save prompt"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { if (!window.confirm("Delete this record?")) return; At.delete(destroyUrl); - }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null))); + }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null)), /* @__PURE__ */ React.createElement( + PromptJsonImportDialog, + { + open: jsonImportOpen, + value: jsonImportValue, + error: jsonImportError, + onChange: setJsonImportValue, + onClose: () => setJsonImportOpen(false), + onApply: applyJsonImport + } + )); } -function GenericEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method }) { - const form = G(record); +function GenericEditor({ title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method, editorContext }) { + const form = G$1(record); + const editorLinks = editorContext?.links || {}; const submit = (event) => { event.preventDefault(); const payload = normalizePayload(fields, form.data); @@ -17230,12 +21055,28 @@ function GenericEditor({ title, subtitle, fields, record, submitUrl, indexUrl, d } form.post(submitUrl); }; - return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se, { title: `Admin · ${title}` }), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-5 rounded-[30px] border border-white/[0.08] bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5" }, fields.map((field) => /* @__PURE__ */ React.createElement(Field$4, { key: field.name, field, form }))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${title}` }), editorLinks.builder || editorLinks.preview ? /* @__PURE__ */ React.createElement("div", { className: "mb-5 flex flex-wrap gap-3" }, editorLinks.builder ? /* @__PURE__ */ React.createElement(xe, { href: editorLinks.builder, className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-5 py-3 text-sm font-semibold text-amber-100" }, "Open builder") : null, editorLinks.preview ? /* @__PURE__ */ React.createElement(xe, { href: editorLinks.preview, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Preview public page") : null) : null, /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "space-y-5 rounded-[30px] border border-white/[0.08] bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5" }, fields.map((field) => /* @__PURE__ */ React.createElement(Field$4, { key: field.name, field, form }))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, form.processing ? "Saving..." : "Save"), /* @__PURE__ */ React.createElement(xe, { href: indexUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white" }, "Back"), destroyUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { if (!window.confirm("Delete this record?")) return; At.delete(destroyUrl); }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-5 py-3 text-sm font-semibold text-rose-100" }, "Delete") : null))); } function AcademyCrudForm({ resource, title, subtitle, fields, record, submitUrl, indexUrl, destroyUrl, method, editorContext }) { + if (resource === "courses") { + return /* @__PURE__ */ React.createElement( + CourseEditor, + { + title, + subtitle, + fields, + record, + submitUrl, + indexUrl, + destroyUrl, + method, + editorContext + } + ); + } if (resource === "lessons") { return /* @__PURE__ */ React.createElement( LessonEditor, @@ -17263,7 +21104,8 @@ function AcademyCrudForm({ resource, title, subtitle, fields, record, submitUrl, submitUrl, indexUrl, destroyUrl, - method + method, + editorContext } ); } @@ -17277,22 +21119,127 @@ function AcademyCrudForm({ resource, title, subtitle, fields, record, submitUrl, submitUrl, indexUrl, destroyUrl, - method + method, + editorContext } ); } -const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyCrudForm }, Symbol.toStringTag, { value: "Module" })); +const PROMPT_VIEW_STORAGE_KEY = "skinbase.admin.academy.prompts.view"; +const PROMPT_VIEW_OPTIONS = [ + { value: "gallery", label: "Gallery", icon: "fa-images" }, + { value: "grid", label: "Grid", icon: "fa-grid-2" }, + { value: "table", label: "Table", icon: "fa-table-list" } +]; +function formatDateLabel$1(value) { + if (!value) return "Recently updated"; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "Recently updated"; + return new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", year: "numeric" }).format(date); +} +function paginationLabel(label) { + return String(label || "").replace(/«/g, "Previous").replace(/»/g, "Next").replace(/<[^>]+>/g, "").trim(); +} +function promptSummary(items = []) { + return items.reduce((summary, item) => ({ + total: summary.total + 1, + active: summary.active + (item.active ? 1 : 0), + featured: summary.featured + (item.featured ? 1 : 0), + promptOfWeek: summary.promptOfWeek + (item.prompt_of_week ? 1 : 0), + comparisons: summary.comparisons + Number(item.comparisons_count || 0) + }), { total: 0, active: 0, featured: 0, promptOfWeek: 0, comparisons: 0 }); +} +function PromptFlag({ children, tone = "default" }) { + const toneClass = tone === "warm" ? "border-[#ffcfbf]/20 bg-[#ffcfbf]/10 text-[#fff0ea]" : tone === "sky" ? "border-sky-300/20 bg-sky-300/10 text-sky-100" : tone === "emerald" ? "border-emerald-300/20 bg-emerald-300/10 text-emerald-100" : "border-white/10 bg-white/[0.05] text-slate-200"; + return /* @__PURE__ */ React.createElement("span", { className: `rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] ${toneClass}` }, children); +} +function PromptActions({ item }) { + return /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, item.preview_url ? /* @__PURE__ */ React.createElement(xe, { href: item.preview_url, className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-4 py-2 text-sm font-semibold text-[#fff0ea]" }, "Preview") : null, /* @__PURE__ */ React.createElement(xe, { href: item.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Edit"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + if (!window.confirm("Delete this prompt?")) return; + At.delete(item.destroy_url, { preserveScroll: true }); + }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Delete")); +} +function PromptPreview({ item, compact = false }) { + if (item.preview_image_url) { + return /* @__PURE__ */ React.createElement("img", { src: item.preview_image_url, alt: item.title, className: `h-full w-full object-cover transition duration-500 ${compact ? "group-hover:scale-[1.04]" : "group-hover:scale-[1.03]"}` }); + } + return /* @__PURE__ */ React.createElement("div", { className: "flex h-full items-center justify-center bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.18),transparent_30%),linear-gradient(135deg,rgba(15,23,42,0.98),rgba(30,41,59,0.94))] p-6 text-center text-slate-300" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Prompt preview"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm font-semibold text-white" }, "No image attached yet"))); +} +function PromptMeta({ item }) { + return /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, item.category_name ? /* @__PURE__ */ React.createElement(PromptFlag, { tone: "warm" }, item.category_name) : null, item.difficulty ? /* @__PURE__ */ React.createElement(PromptFlag, null, item.difficulty) : null, item.access_level ? /* @__PURE__ */ React.createElement(PromptFlag, null, item.access_level) : null, item.aspect_ratio ? /* @__PURE__ */ React.createElement(PromptFlag, null, item.aspect_ratio) : null, item.featured ? /* @__PURE__ */ React.createElement(PromptFlag, { tone: "sky" }, "Featured") : null, item.prompt_of_week ? /* @__PURE__ */ React.createElement(PromptFlag, { tone: "emerald" }, "Prompt of week") : null, /* @__PURE__ */ React.createElement(PromptFlag, { tone: item.active ? "sky" : "default" }, item.active ? "Active" : "Draft")); +} +function PromptGalleryCard({ item }) { + return /* @__PURE__ */ React.createElement("article", { className: "group overflow-hidden rounded-[30px] border border-white/[0.08] bg-[linear-gradient(135deg,rgba(8,15,28,0.98),rgba(15,23,42,0.92))] shadow-[0_24px_80px_rgba(2,6,23,0.24)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 xl:grid-cols-[340px_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "relative min-h-[250px] overflow-hidden border-b border-white/10 xl:min-h-full xl:border-b-0 xl:border-r xl:border-white/10" }, /* @__PURE__ */ React.createElement(PromptPreview, { item }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,rgba(2,6,23,0.04),rgba(2,6,23,0.32))]" }), /* @__PURE__ */ React.createElement("div", { className: "absolute left-4 top-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement(PromptFlag, { tone: "warm" }, item.comparisons_count || 0, " comparisons"), item.slug ? /* @__PURE__ */ React.createElement(PromptFlag, null, item.slug) : null)), /* @__PURE__ */ React.createElement("div", { className: "flex h-full flex-col justify-between p-6 lg:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(PromptMeta, { item }), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.04em] text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, item.excerpt || "Add an excerpt to make this prompt easier to scan in moderation."), item.tags?.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-2" }, item.tags.slice(0, 5).map((tag) => /* @__PURE__ */ React.createElement("span", { key: tag, className: "rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-slate-300" }, tag))) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 flex flex-wrap items-center justify-between gap-4 border-t border-white/10 pt-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Updated"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm font-semibold text-white" }, formatDateLabel$1(item.updated_at))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Access"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm font-semibold text-white" }, item.access_level || "free")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Status"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm font-semibold text-white" }, item.active ? "Visible" : "Hidden"))), /* @__PURE__ */ React.createElement(PromptActions, { item }))))); +} +function PromptGridCard({ item }) { + return /* @__PURE__ */ React.createElement("article", { className: "group overflow-hidden rounded-[28px] border border-white/[0.08] bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(15,23,42,0.18))] shadow-[0_18px_60px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "relative h-52 overflow-hidden border-b border-white/10" }, /* @__PURE__ */ React.createElement(PromptPreview, { item, compact: true }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,rgba(2,6,23,0.02),rgba(2,6,23,0.34))]" })), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement(PromptMeta, { item }), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-xl font-semibold tracking-[-0.04em] text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.excerpt || "No excerpt added yet."), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 text-sm text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, formatDateLabel$1(item.updated_at)), /* @__PURE__ */ React.createElement("span", null, item.comparisons_count || 0, " comparisons")), /* @__PURE__ */ React.createElement("div", { className: "mt-5" }, /* @__PURE__ */ React.createElement(PromptActions, { item })))); +} +function PromptTable({ items }) { + return /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[30px] border border-white/[0.08] bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(2,6,23,0.92))] shadow-[0_24px_80px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "min-w-full divide-y divide-white/10 text-left" }, /* @__PURE__ */ React.createElement("thead", { className: "bg-white/[0.04] text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4" }, "Prompt"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4" }, "Category"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4" }, "Access"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4" }, "Signals"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4" }, "Updated"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-4 text-right" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/10 text-sm text-slate-200" }, items.map((item) => /* @__PURE__ */ React.createElement("tr", { key: item.id, className: "align-top transition hover:bg-white/[0.03]" }, /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "h-16 w-20 overflow-hidden rounded-2xl border border-white/10 bg-black/30 shrink-0" }, /* @__PURE__ */ React.createElement(PromptPreview, { item, compact: true })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 max-w-md text-sm leading-6 text-slate-400" }, item.excerpt || "No excerpt added yet.")))), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, item.category_name || "Uncategorized"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, item.access_level || "free"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React.createElement("p", null, item.comparisons_count || 0, " comparisons"), /* @__PURE__ */ React.createElement("p", null, item.difficulty || "No difficulty"), /* @__PURE__ */ React.createElement("p", null, item.active ? "Active" : "Draft"))), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, formatDateLabel$1(item.updated_at)), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex justify-end gap-2" }, item.preview_url ? /* @__PURE__ */ React.createElement(xe, { href: item.preview_url, className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-3 py-2 text-xs font-semibold text-[#fff0ea]" }, "Preview") : null, /* @__PURE__ */ React.createElement(xe, { href: item.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-xs font-semibold text-white" }, "Edit"))))))))); +} +function PromptHeroCollage({ items = [] }) { + const images = items.map((item) => item?.preview_image_url).filter(Boolean).slice(0, 4); + if (!images.length) { + return /* @__PURE__ */ React.createElement("div", { className: "flex min-h-[420px] items-center justify-center rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_24%),radial-gradient(circle_at_bottom_right,rgba(255,207,191,0.18),transparent_26%),linear-gradient(135deg,rgba(12,18,31,0.98),rgba(30,41,59,0.94))] px-8 text-center" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500" }, "Prompt preview wall"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-lg font-semibold text-white" }, "Preview images will appear here as prompts get covers."))); + } + return /* @__PURE__ */ React.createElement("div", { className: "grid min-h-[420px] grid-cols-2 gap-3" }, images.map((image2, index2) => /* @__PURE__ */ React.createElement( + "div", + { + key: `${image2}-${index2}`, + className: `overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.2)] ${index2 === 0 ? "col-span-2 aspect-[16/9]" : index2 === 3 ? "aspect-[4/5]" : "aspect-square"}` + }, + /* @__PURE__ */ React.createElement("img", { src: image2, alt: "", "aria-hidden": "true", className: "h-full w-full object-cover" }) + ))); +} +function PaginationLinks({ links = [] }) { + if (!Array.isArray(links) || links.length <= 3) return null; + return /* @__PURE__ */ React.createElement("div", { className: "mt-8 flex flex-wrap gap-2" }, links.map((link2, index2) => { + const label = paginationLabel(link2.label); + const className = link2.active ? "border-sky-300/25 bg-sky-300/12 text-sky-100" : "border-white/10 bg-white/[0.04] text-slate-200 hover:border-white/20 hover:bg-white/[0.07]"; + return link2.url ? /* @__PURE__ */ React.createElement(xe, { key: `${label}-${index2}`, href: link2.url, className: `rounded-full border px-4 py-2 text-sm font-semibold transition ${className}`, preserveScroll: true }, label) : /* @__PURE__ */ React.createElement("span", { key: `${label}-${index2}`, className: "rounded-full border border-white/10 bg-black/20 px-4 py-2 text-sm font-semibold text-slate-500" }, label); + })); +} +function PromptIndexContent({ title, subtitle, items, createUrl }) { + const promptItems = items?.data || []; + const summary = promptSummary(promptItems); + const [viewMode, setViewMode] = reactExports.useState("gallery"); + reactExports.useEffect(() => { + if (typeof window === "undefined") return; + const storedView = window.localStorage.getItem(PROMPT_VIEW_STORAGE_KEY); + if (PROMPT_VIEW_OPTIONS.some((option) => option.value === storedView)) { + setViewMode(storedView); + } + }, []); + reactExports.useEffect(() => { + if (typeof window === "undefined") return; + window.localStorage.setItem(PROMPT_VIEW_STORAGE_KEY, viewMode); + }, [viewMode]); + return /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[38px] border border-white/[0.08] bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_24%),radial-gradient(circle_at_bottom_right,rgba(255,207,191,0.16),transparent_24%),linear-gradient(135deg,rgba(4,9,18,0.98),rgba(15,23,42,0.92))] shadow-[0_28px_90px_rgba(2,6,23,0.28)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 p-6 xl:grid-cols-[minmax(0,1.08fr)_420px] xl:items-end xl:p-10" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-[#ffcfbf]/20 bg-[#ffcfbf]/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-[#fff0ea]" }, "Academy moderation"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-300" }, "Prompt library")), /* @__PURE__ */ React.createElement("h2", { className: "mt-5 max-w-4xl text-4xl font-semibold tracking-[-0.055em] text-white md:text-5xl xl:text-6xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg" }, subtitle, " Review prompts in a visual-first moderation surface, jump into edits quickly, and switch between gallery, grid, or table depending on the task in front of you."), /* @__PURE__ */ React.createElement("div", { className: "mt-7 grid gap-3 sm:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Visual-first"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "Curate covers and prompt outputs before opening the form.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Workflow-ready"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "Switch between gallery, compact cards, and scan-heavy tables.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Comparison-aware"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm font-semibold text-white" }, "Spot prompts with provider notes and attached result references."))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 flex flex-wrap gap-3" }, PROMPT_VIEW_OPTIONS.map((option) => { + const active = option.value === viewMode; + return /* @__PURE__ */ React.createElement( + "button", + { + key: option.value, + type: "button", + onClick: () => setViewMode(option.value), + className: `inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-semibold transition ${active ? "border-sky-300/25 bg-sky-300/12 text-sky-100" : "border-white/10 bg-white/[0.04] text-slate-200 hover:border-white/20 hover:bg-white/[0.07]"}` + }, + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${option.icon}` }), + /* @__PURE__ */ React.createElement("span", null, option.label, " view") + ); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-7 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Create prompt"), /* @__PURE__ */ React.createElement(xe, { href: "/academy/prompts", className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white/85" }, "Open public library"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white/85" }, summary.total, " prompts in view")), /* @__PURE__ */ React.createElement("div", { className: "mt-7 grid gap-3 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Active"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, summary.active)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Featured"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, summary.featured)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Prompt of week"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, summary.promptOfWeek)), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-5 py-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Comparisons"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, summary.comparisons)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(PromptHeroCollage, { items: promptItems })))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "Manage Academy content below. Changes clear Academy cache automatically."), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: "/academy/prompts", className: "rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white/85" }, "View public library"), /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Create prompt"))), promptItems.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] px-6 py-12 text-center text-slate-400" }, "No prompt templates exist yet.") : viewMode === "table" ? /* @__PURE__ */ React.createElement(PromptTable, { items: promptItems }) : viewMode === "grid" ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 md:grid-cols-2 2xl:grid-cols-3" }, promptItems.map((item) => /* @__PURE__ */ React.createElement(PromptGridCard, { key: item.id, item }))) : /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, promptItems.map((item) => /* @__PURE__ */ React.createElement(PromptGalleryCard, { key: item.id, item }))), /* @__PURE__ */ React.createElement(PaginationLinks, { links: items?.links })); +} function AcademyCrudIndex({ title, subtitle, items, columns, createUrl }) { - const flash = X().props.flash || {}; - return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se, { title: `Admin · ${title}` }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "Manage Academy content below. Changes clear Academy cache automatically."), /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Create record")), (items?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] px-6 py-12 text-center text-slate-400" }, "No records exist yet.") : /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, items.data.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-5" }, columns.map((column) => /* @__PURE__ */ React.createElement("div", { key: column }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, column.replaceAll("_", " ")), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-white" }, String(item[column] ?? ""))))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, /* @__PURE__ */ React.createElement(xe, { href: item.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Edit"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { + const flash = X$1().props.flash || {}; + return /* @__PURE__ */ React.createElement(AdminLayout, { title, subtitle }, /* @__PURE__ */ React.createElement(Se$1, { title: `Admin · ${title}` }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, X$1().props.resource === "prompts" ? /* @__PURE__ */ React.createElement(PromptIndexContent, { title, subtitle, items, createUrl }) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "Manage Academy content below. Changes clear Academy cache automatically."), /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100" }, "Create record")), (items?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] px-6 py-12 text-center text-slate-400" }, "No records exist yet.") : /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, items.data.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-5" }, columns.map((column) => /* @__PURE__ */ React.createElement("div", { key: column }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, column.replaceAll("_", " ")), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-white" }, String(item[column] ?? ""))))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, item.builder_url ? /* @__PURE__ */ React.createElement(xe, { href: item.builder_url, className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-4 py-2 text-sm font-semibold text-amber-100" }, "Builder") : null, /* @__PURE__ */ React.createElement(xe, { href: item.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Edit"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => { if (!window.confirm("Delete this record?")) return; At.delete(item.destroy_url, { preserveScroll: true }); - }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Delete"))))))); + }, className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Delete")))))))); } -const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_10 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyCrudIndex }, Symbol.toStringTag, { value: "Module" })); @@ -17300,17 +21247,17 @@ function StatCard$c({ label, value }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/[0.08] bg-white/[0.04] p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-3xl font-bold text-white" }, value.toLocaleString())); } function AcademyDashboard({ stats, links }) { - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Academy Dashboard", subtitle: "Overview of Academy content, challenge activity, and future billing placeholders." }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Academy Dashboard" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatCard$c, { label: "Lessons", value: stats.lessons }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Prompts", value: stats.prompts }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Prompt Packs", value: stats.packs }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Challenges", value: stats.challenges }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Submissions", value: stats.submissions }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Badges", value: stats.badges }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Creator Subscribers", value: stats.creator_subscribers }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Pro Subscribers", value: stats.pro_subscribers })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Modules"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3" }, Object.entries(links).map(([key, href]) => /* @__PURE__ */ React.createElement(xe, { key, href, className: "rounded-2xl border border-white/[0.08] bg-black/20 px-4 py-4 text-sm font-semibold text-white transition hover:border-white/15 hover:bg-white/[0.05]" }, key.replaceAll("_", " ")))))); + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Academy Dashboard", subtitle: "Overview of Academy content, challenge activity, and future billing placeholders." }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Academy Dashboard" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatCard$c, { label: "Courses", value: stats.courses }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Lessons", value: stats.lessons }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Prompts", value: stats.prompts }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Prompt Packs", value: stats.packs }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Challenges", value: stats.challenges }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Submissions", value: stats.submissions }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Badges", value: stats.badges }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Creator Subscribers", value: stats.creator_subscribers }), /* @__PURE__ */ React.createElement(StatCard$c, { label: "Pro Subscribers", value: stats.pro_subscribers })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Modules"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3" }, Object.entries(links).map(([key, href]) => /* @__PURE__ */ React.createElement(xe, { key, href, className: "rounded-2xl border border-white/[0.08] bg-black/20 px-4 py-4 text-sm font-semibold text-white transition hover:border-white/15 hover:bg-white/[0.05]" }, key.replaceAll("_", " ")))))); } -const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_11 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademyDashboard }, Symbol.toStringTag, { value: "Module" })); function AcademySubmissions({ submissions }) { - const flash = X().props.flash || {}; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Academy Challenge Submissions", subtitle: "Approve or reject Academy challenge entries." }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Academy Challenge Submissions" }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, (submissions?.data || []).map((submission) => /* @__PURE__ */ React.createElement("article", { key: submission.id, className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-white/80" }, submission.moderation_status), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, submission.challenge?.title || "Challenge")), /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, submission.artwork?.title || "Artwork removed"), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, submission.user?.name || "Unknown user", " · ", submission.ai_tool_used || "No tool noted"), submission.prompt_used ? /* @__PURE__ */ React.createElement("pre", { className: "whitespace-pre-wrap rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-200" }, submission.prompt_used) : null, submission.workflow_notes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-300" }, submission.workflow_notes) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(submission.approve_url, {}, { preserveScroll: true }), className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-4 py-2 text-sm font-semibold text-emerald-100" }, "Approve"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(submission.reject_url, {}, { preserveScroll: true }), className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Reject"))))))); + const flash = X$1().props.flash || {}; + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Academy Challenge Submissions", subtitle: "Approve or reject Academy challenge entries." }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Academy Challenge Submissions" }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, (submissions?.data || []).map((submission) => /* @__PURE__ */ React.createElement("article", { key: submission.id, className: "rounded-[28px] border border-white/[0.08] bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-white/80" }, submission.moderation_status), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, submission.challenge?.title || "Challenge")), /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, submission.artwork?.title || "Artwork removed"), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, submission.user?.name || "Unknown user", " · ", submission.ai_tool_used || "No tool noted"), submission.prompt_used ? /* @__PURE__ */ React.createElement("pre", { className: "whitespace-pre-wrap rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-200" }, submission.prompt_used) : null, submission.workflow_notes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-sm leading-7 text-slate-300" }, submission.workflow_notes) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(submission.approve_url, {}, { preserveScroll: true }), className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-4 py-2 text-sm font-semibold text-emerald-100" }, "Approve"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(submission.reject_url, {}, { preserveScroll: true }), className: "rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Reject"))))))); } -const __vite_glob_0_9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_13 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AcademySubmissions }, Symbol.toStringTag, { value: "Module" })); @@ -17377,7 +21324,7 @@ function labelForStatus(value) { return String(value).replaceAll("_", " "); } function AiBiographyAdmin() { - const { props } = X(); + const { props } = X$1(); const records = props.records || { data: [] }; const stats = props.stats || {}; const endpoints = props.endpoints || {}; @@ -17434,7 +21381,7 @@ function AiBiographyAdmin() { setBusyKey(""); } } - return /* @__PURE__ */ React.createElement("div", { className: "w-full pb-16 pt-8" }, /* @__PURE__ */ React.createElement(Se, { title: "AI Biography Review" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.2),transparent_32%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.9))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/80" }, "Moderator surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "AI biography review"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-relaxed text-slate-300" }, "Browse active biographies and historical generations, inspect review flags and failures, and rebuild a creator biography directly from cPad.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 text-xs uppercase tracking-[0.16em] text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2" }, "Page ", records.current_page || 1, " / ", records.last_page || 1), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2" }, Number(records.total || 0).toLocaleString(), " records"))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(StatCard$b, { label: "Total records", value: stats.total_records, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Active", value: stats.active_records, tone: "emerald" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Needs review", value: stats.needs_review, tone: "amber" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Hidden active", value: stats.hidden_active, tone: "slate" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Failed", value: stats.failed, tone: "rose" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "User edited", value: stats.user_edited_active, tone: "sky" })), /* @__PURE__ */ React.createElement("form", { onSubmit: applyFilters, className: "mt-6 grid gap-3 lg:grid-cols-[2fr_repeat(5,minmax(0,1fr))]" }, /* @__PURE__ */ React.createElement("label", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Search creator"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement("div", { className: "w-full pb-16 pt-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "AI Biography Review" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.2),transparent_32%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.9))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/80" }, "Moderator surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "AI biography review"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-relaxed text-slate-300" }, "Browse active biographies and historical generations, inspect review flags and failures, and rebuild a creator biography directly from cPad.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 text-xs uppercase tracking-[0.16em] text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2" }, "Page ", records.current_page || 1, " / ", records.last_page || 1), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2" }, Number(records.total || 0).toLocaleString(), " records"))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(StatCard$b, { label: "Total records", value: stats.total_records, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Active", value: stats.active_records, tone: "emerald" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Needs review", value: stats.needs_review, tone: "amber" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Hidden active", value: stats.hidden_active, tone: "slate" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "Failed", value: stats.failed, tone: "rose" }), /* @__PURE__ */ React.createElement(StatCard$b, { label: "User edited", value: stats.user_edited_active, tone: "sky" })), /* @__PURE__ */ React.createElement("form", { onSubmit: applyFilters, className: "mt-6 grid gap-3 lg:grid-cols-[2fr_repeat(5,minmax(0,1fr))]" }, /* @__PURE__ */ React.createElement("label", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Search creator"), /* @__PURE__ */ React.createElement( "input", { value: filters.q || "", @@ -17499,20 +21446,20 @@ function AiBiographyAdmin() { )), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2 text-xs leading-relaxed text-slate-300" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-slate-100" }, "Approved:"), " ", formatDateTime$5(record.approved_at)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-slate-100" }, "Created:"), " ", formatDateTime$5(record.created_at)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-slate-100" }, "Updated:"), " ", formatDateTime$5(record.updated_at)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-slate-100" }, "Source hash:"), " ", record.source_hash || "—"))))); })), records.prev_page_url || records.next_page_url ? /* @__PURE__ */ React.createElement("div", { className: "mt-8 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, records.prev_page_url ? /* @__PURE__ */ React.createElement(xe, { href: records.prev_page_url, preserveScroll: true, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left text-[10px]" }), "Previous") : null), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, "Showing page ", records.current_page || 1, " of ", records.last_page || 1), /* @__PURE__ */ React.createElement("div", null, records.next_page_url ? /* @__PURE__ */ React.createElement(xe, { href: records.next_page_url, preserveScroll: true, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]" }, "Next", /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right text-[10px]" })) : null)) : null); } -const __vite_glob_0_76 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_80 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AiBiographyAdmin }, Symbol.toStringTag, { value: "Module" })); function AdminAiBiography() { return /* @__PURE__ */ React.createElement(AdminLayout, null, /* @__PURE__ */ React.createElement(AiBiographyAdmin, null)); } -const __vite_glob_0_10 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_14 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AdminAiBiography }, Symbol.toStringTag, { value: "Module" })); function AdminArtworks({ artworks }) { const items = artworks?.data ?? []; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Artworks", subtitle: "Browse and manage all artworks on the platform" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Artworks" }), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-white/[0.07] bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-600" }, /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Artwork"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Author"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Status"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Uploaded"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5 text-right" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/[0.04]" }, items.length === 0 && /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: 5, className: "px-5 py-12 text-center text-slate-500" }, "No artworks found.")), items.map((artwork) => /* @__PURE__ */ React.createElement("tr", { key: artwork.id, className: "transition hover:bg-white/[0.025]" }, /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, artwork.thumb && /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "h-10 w-10 rounded-lg object-cover" }), /* @__PURE__ */ React.createElement("span", { className: "font-medium text-white" }, artwork.title || /* @__PURE__ */ React.createElement("span", { className: "italic text-slate-500" }, "Untitled")))), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-400" }, artwork.user?.name ?? "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("span", { className: `inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${artwork.status === "published" ? "bg-teal-500/20 text-teal-300" : artwork.status === "pending" ? "bg-amber-500/20 text-amber-300" : "bg-slate-500/20 text-slate-400"}` }, artwork.status ?? "unknown")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-500" }, artwork.created_at ? new Date(artwork.created_at).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-right" }, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Artworks", subtitle: "Browse and manage all artworks on the platform" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Artworks" }), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-white/[0.07] bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-600" }, /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Artwork"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Author"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Status"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Uploaded"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5 text-right" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/[0.04]" }, items.length === 0 && /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: 5, className: "px-5 py-12 text-center text-slate-500" }, "No artworks found.")), items.map((artwork) => /* @__PURE__ */ React.createElement("tr", { key: artwork.id, className: "transition hover:bg-white/[0.025]" }, /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, artwork.thumb && /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "h-10 w-10 rounded-lg object-cover" }), /* @__PURE__ */ React.createElement("span", { className: "font-medium text-white" }, artwork.title || /* @__PURE__ */ React.createElement("span", { className: "italic text-slate-500" }, "Untitled")))), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-400" }, artwork.user?.name ?? "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("span", { className: `inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${artwork.status === "published" ? "bg-teal-500/20 text-teal-300" : artwork.status === "pending" ? "bg-amber-500/20 text-amber-300" : "bg-slate-500/20 text-slate-400"}` }, artwork.status ?? "unknown")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-500" }, artwork.created_at ? new Date(artwork.created_at).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-right" }, /* @__PURE__ */ React.createElement( "a", { href: `/studio/artworks/${artwork.id}/edit`, @@ -17530,7 +21477,7 @@ function AdminArtworks({ artworks }) { } ) : /* @__PURE__ */ React.createElement("span", { key: i, className: "rounded-lg px-3 py-1.5 text-xs text-slate-700", dangerouslySetInnerHTML: { __html: link2.label } })))))); } -const __vite_glob_0_11 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_15 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AdminArtworks }, Symbol.toStringTag, { value: "Module" })); @@ -17593,7 +21540,7 @@ function AuthAudit({ logs, filters, eventOptions, statusOptions }) { preserveScroll: true }); }; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Auth Audit", subtitle: "Review login, registration, forgot-password, and reset-password activity with IPs, timestamps, status, and failure reasons." }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Auth Audit" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Visible records", value: items.length.toLocaleString(), tone: "slate" }), /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Failures on page", value: failedCount.toLocaleString(), tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Unique IPs on page", value: uniqueIpCount.toLocaleString(), tone: "sky" })), /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[28px] border border-white/[0.07] bg-white/[0.02] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "flex flex-1 flex-col gap-3 md:flex-row" }, /* @__PURE__ */ React.createElement("label", { className: "flex-1" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block text-xs font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Auth Audit", subtitle: "Review login, registration, forgot-password, and reset-password activity with IPs, timestamps, status, and failure reasons." }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Auth Audit" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Visible records", value: items.length.toLocaleString(), tone: "slate" }), /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Failures on page", value: failedCount.toLocaleString(), tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$5, { label: "Unique IPs on page", value: uniqueIpCount.toLocaleString(), tone: "sky" })), /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[28px] border border-white/[0.07] bg-white/[0.02] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "flex flex-1 flex-col gap-3 md:flex-row" }, /* @__PURE__ */ React.createElement("label", { className: "flex-1" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block text-xs font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search"), /* @__PURE__ */ React.createElement( "input", { name: "search", @@ -17628,7 +21575,7 @@ function AuthAudit({ logs, filters, eventOptions, statusOptions }) { } ) : /* @__PURE__ */ React.createElement("span", { key: `${link2.label}-${index2}`, className: "rounded-lg px-3 py-1.5 text-xs text-slate-700", dangerouslySetInnerHTML: { __html: link2.label } })))) : null)); } -const __vite_glob_0_12 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_16 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AuthAudit }, Symbol.toStringTag, { value: "Module" })); @@ -17671,7 +21618,7 @@ function DailyActivity({ selectedDate, summary, queues, sections }) { const onDateChange = (event) => { At.get("/moderation/activity", { date: event.target.value }, { preserveState: true, replace: true }); }; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Daily Activity", subtitle: "A day-by-day moderation cockpit for reviewing new content, queue movement, and staff actions." }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Daily Activity" }), /* @__PURE__ */ React.createElement("div", { className: "rounded-3xl border border-white/10 bg-[linear-gradient(135deg,rgba(244,63,94,0.18),rgba(15,23,42,0.75))] p-6 shadow-[0_30px_120px_rgba(15,23,42,0.5)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-5" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-2xl" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.26em] text-rose-200/80" }, "Moderation review"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-bold text-white" }, "Selected day: ", selectedDate), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-200/80" }, "This view pulls together the moderation-adjacent activity for the selected day so admins can triage queues and jump into the right review surface quickly.")), /* @__PURE__ */ React.createElement("label", { className: "block" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-300" }, "Day"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Daily Activity", subtitle: "A day-by-day moderation cockpit for reviewing new content, queue movement, and staff actions." }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Daily Activity" }), /* @__PURE__ */ React.createElement("div", { className: "rounded-3xl border border-white/10 bg-[linear-gradient(135deg,rgba(244,63,94,0.18),rgba(15,23,42,0.75))] p-6 shadow-[0_30px_120px_rgba(15,23,42,0.5)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-5" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-2xl" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.26em] text-rose-200/80" }, "Moderation review"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-bold text-white" }, "Selected day: ", selectedDate), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-200/80" }, "This view pulls together the moderation-adjacent activity for the selected day so admins can triage queues and jump into the right review surface quickly.")), /* @__PURE__ */ React.createElement("label", { className: "block" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-300" }, "Day"), /* @__PURE__ */ React.createElement( "input", { type: "date", @@ -17762,7 +21709,7 @@ function DailyActivity({ selectedDate, summary, queues, sections }) { } ))))); } -const __vite_glob_0_13 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_17 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: DailyActivity }, Symbol.toStringTag, { value: "Module" })); @@ -17776,7 +21723,7 @@ function StatCard$9({ icon, label, value, color: color2 = "sky" }) { return /* @__PURE__ */ React.createElement("div", { className: `rounded-2xl border bg-gradient-to-br p-6 ${colors[color2]}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-wider text-slate-400" }, label), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-3xl font-bold text-white" }, value.toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: `flex h-12 w-12 items-center justify-center rounded-xl bg-white/5` }, /* @__PURE__ */ React.createElement("i", { className: `${icon} text-xl ${colors[color2].split(" ").at(-1)}` })))); } function Dashboard({ stats }) { - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Dashboard", subtitle: "Overview of your Skinbase platform" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin Dashboard" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-users", label: "Total Users", value: stats.total_users, color: "sky" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-user-plus", label: "New Today", value: stats.new_users_today, color: "violet" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-shield-halved", label: "Staff Members", value: stats.staff_count, color: "rose" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-user-shield", label: "Moderators", value: stats.moderator_count, color: "amber" })), /* @__PURE__ */ React.createElement("div", { className: "mt-10" }, /* @__PURE__ */ React.createElement("h2", { className: "mb-4 text-sm font-semibold uppercase tracking-wider text-slate-500" }, "Quick Actions"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" }, [ + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Dashboard", subtitle: "Overview of your Skinbase platform" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin Dashboard" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-users", label: "Total Users", value: stats.total_users, color: "sky" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-user-plus", label: "New Today", value: stats.new_users_today, color: "violet" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-shield-halved", label: "Staff Members", value: stats.staff_count, color: "rose" }), /* @__PURE__ */ React.createElement(StatCard$9, { icon: "fa-solid fa-user-shield", label: "Moderators", value: stats.moderator_count, color: "amber" })), /* @__PURE__ */ React.createElement("div", { className: "mt-10" }, /* @__PURE__ */ React.createElement("h2", { className: "mb-4 text-sm font-semibold uppercase tracking-wider text-slate-500" }, "Quick Actions"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" }, [ { label: "Daily Activity", href: "/moderation/activity", icon: "fa-solid fa-calendar-day", desc: "Review everything created or moderated on a selected day" }, { label: "Manage Users", href: "/moderation/users", icon: "fa-solid fa-users", desc: "Search, promote or demote users" }, { label: "Staff Roles", href: "/moderation/users?role=admin", icon: "fa-solid fa-shield-halved", desc: "View all admins, managers and editorial staff" }, @@ -17797,7 +21744,7 @@ function Dashboard({ stats }) { /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white group-hover:text-rose-300 transition" }, item.label), /* @__PURE__ */ React.createElement("p", { className: "mt-0.5 text-xs text-slate-500" }, item.desc)) ))))); } -const __vite_glob_0_14 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_18 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: Dashboard }, Symbol.toStringTag, { value: "Module" })); @@ -17997,7 +21944,7 @@ function compareEntries(left, right, sortKey, direction) { return Number(right.id || 0) - Number(left.id || 0); } function FeaturedArtworksAdmin() { - const { props } = X(); + const { props } = X$1(); const endpoints = props.endpoints || {}; const capabilities = props.capabilities || {}; const seo = props.seo || {}; @@ -18179,7 +22126,7 @@ function FeaturedArtworksAdmin() { }).sort((left, right) => compareEntries(left, right, sortKey, sortDirection)); }, [entries, filter2, listQuery, sortDirection, sortKey]); const duplicateSelection = !editingId && selectedArtwork?.already_featured; - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Featured Artworks"), seo.description ? /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description }) : null, seo.robots ? /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots }) : null), /* @__PURE__ */ React.createElement("div", { className: "min-h-screen bg-[#07111c] text-white" }, /* @__PURE__ */ React.createElement("div", { className: "mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_35%),radial-gradient(circle_at_bottom_right,_rgba(245,158,11,0.14),_transparent_35%),linear-gradient(180deg,_rgba(6,14,25,0.92),_rgba(8,18,32,0.96))] p-8 shadow-[0_28px_90px_rgba(2,6,23,0.45)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, /* @__PURE__ */ React.createElement("div", { className: "inline-flex rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Featured Artworks"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold tracking-[-0.05em] text-white sm:text-5xl" }, "Homepage hero control, with the real winner logic exposed."), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-2xl text-sm leading-7 text-slate-300 sm:text-base" }, "Editors can create, update, activate, expire, and remove featured entries here. The winner summary below mirrors the public homepage selection order: priority, recent medal score, featured date, then published date.")), /* @__PURE__ */ React.createElement("div", { className: "grid w-full max-w-xl grid-cols-2 gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement(StatCard$8, { label: "Entries", value: stats.total || 0, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Eligible", value: stats.eligible || 0, tone: "emerald" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Expired", value: stats.expired || 0, tone: "amber" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Active", value: stats.active || 0, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Inactive", value: stats.inactive || 0, tone: "rose" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Not Eligible", value: stats.ineligible || 0, tone: "rose" })))), notice ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-sky-300/15 bg-sky-400/10 px-4 py-3 text-sm text-sky-50" }, notice) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 lg:grid-cols-[1.1fr_0.9fr]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Current Homepage Hero"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, winner ? winner.artwork?.title : "No eligible featured artwork"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-7 text-slate-300" }, winner?.selection_reason || "There is no active, non-expired, eligible featured artwork right now."), winner?.is_force_hero ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 max-w-2xl rounded-2xl border border-amber-300/20 bg-amber-400/10 px-4 py-3 text-sm leading-6 text-amber-50" }, "Forced by editor. This artwork bypasses the normal hero winner order until Force Hero is disabled on its featured row.") : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, winner ? /* @__PURE__ */ React.createElement(Badge$2, { label: "Winner", tone: "amber" }) : /* @__PURE__ */ React.createElement(Badge$2, { label: "No Winner", tone: "rose" }), winner?.is_force_hero ? /* @__PURE__ */ React.createElement(Badge$2, { label: "Force Hero", tone: "amber" }) : null)), winner ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 lg:grid-cols-[220px_1fr]" }, /* @__PURE__ */ React.createElement("a", { href: winner.artwork?.canonical_url || "#", className: "overflow-hidden rounded-[24px] border border-white/10 bg-[#09121f]", target: "_blank", rel: "noreferrer" }, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Featured Artworks"), seo.description ? /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description }) : null, seo.robots ? /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots }) : null), /* @__PURE__ */ React.createElement("div", { className: "min-h-screen bg-[#07111c] text-white" }, /* @__PURE__ */ React.createElement("div", { className: "mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_35%),radial-gradient(circle_at_bottom_right,_rgba(245,158,11,0.14),_transparent_35%),linear-gradient(180deg,_rgba(6,14,25,0.92),_rgba(8,18,32,0.96))] p-8 shadow-[0_28px_90px_rgba(2,6,23,0.45)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, /* @__PURE__ */ React.createElement("div", { className: "inline-flex rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Featured Artworks"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold tracking-[-0.05em] text-white sm:text-5xl" }, "Homepage hero control, with the real winner logic exposed."), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-2xl text-sm leading-7 text-slate-300 sm:text-base" }, "Editors can create, update, activate, expire, and remove featured entries here. The winner summary below mirrors the public homepage selection order: priority, recent medal score, featured date, then published date.")), /* @__PURE__ */ React.createElement("div", { className: "grid w-full max-w-xl grid-cols-2 gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement(StatCard$8, { label: "Entries", value: stats.total || 0, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Eligible", value: stats.eligible || 0, tone: "emerald" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Expired", value: stats.expired || 0, tone: "amber" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Active", value: stats.active || 0, tone: "sky" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Inactive", value: stats.inactive || 0, tone: "rose" }), /* @__PURE__ */ React.createElement(StatCard$8, { label: "Not Eligible", value: stats.ineligible || 0, tone: "rose" })))), notice ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-sky-300/15 bg-sky-400/10 px-4 py-3 text-sm text-sky-50" }, notice) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 lg:grid-cols-[1.1fr_0.9fr]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Current Homepage Hero"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold tracking-[-0.04em] text-white" }, winner ? winner.artwork?.title : "No eligible featured artwork"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-7 text-slate-300" }, winner?.selection_reason || "There is no active, non-expired, eligible featured artwork right now."), winner?.is_force_hero ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 max-w-2xl rounded-2xl border border-amber-300/20 bg-amber-400/10 px-4 py-3 text-sm leading-6 text-amber-50" }, "Forced by editor. This artwork bypasses the normal hero winner order until Force Hero is disabled on its featured row.") : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, winner ? /* @__PURE__ */ React.createElement(Badge$2, { label: "Winner", tone: "amber" }) : /* @__PURE__ */ React.createElement(Badge$2, { label: "No Winner", tone: "rose" }), winner?.is_force_hero ? /* @__PURE__ */ React.createElement(Badge$2, { label: "Force Hero", tone: "amber" }) : null)), winner ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 lg:grid-cols-[220px_1fr]" }, /* @__PURE__ */ React.createElement("a", { href: winner.artwork?.canonical_url || "#", className: "overflow-hidden rounded-[24px] border border-white/10 bg-[#09121f]", target: "_blank", rel: "noreferrer" }, /* @__PURE__ */ React.createElement( "img", { src: winner.artwork?.thumbnail?.url, @@ -18249,14 +22196,14 @@ function FeaturedArtworksAdmin() { } ), /* @__PURE__ */ React.createElement(NovaSelect, { value: filter2, onChange: (val) => setFilter(val), searchable: false, options: [{ value: "all", label: "All rows" }, { value: "active", label: "Active" }, { value: "inactive", label: "Inactive" }, { value: "expired", label: "Expired" }, { value: "winner", label: "Winner" }, { value: "eligible", label: "Eligible" }, { value: "ineligible", label: "Not eligible" }] }), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-[1fr_auto] gap-3" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: sortKey, onChange: (val) => setSortKey(val), searchable: false, options: [{ value: "priority", label: "Priority" }, { value: "featured_at", label: "Featured Since" }, { value: "expires_at", label: "Expires" }, { value: "score_30d", label: "Medal Score (30d)" }] }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSortDirection((current) => current === "desc" ? "asc" : "desc"), className: "rounded-2xl border border-white/10 px-4 py-3 text-sm font-semibold text-slate-100 transition hover:border-white/20 hover:bg-white/5" }, sortDirection === "desc" ? "Desc" : "Asc")))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 overflow-hidden rounded-[24px] border border-white/10" }, /* @__PURE__ */ React.createElement("div", { className: "hidden grid-cols-[1.2fr_1fr_0.5fr_0.9fr_0.9fr_0.7fr_1.5fr_0.9fr] gap-4 border-b border-white/10 bg-black/20 px-5 py-4 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400 lg:grid" }, /* @__PURE__ */ React.createElement("div", null, "Artwork"), /* @__PURE__ */ React.createElement("div", null, "Artist / Owner"), /* @__PURE__ */ React.createElement("div", null, "Priority"), /* @__PURE__ */ React.createElement("div", null, "Featured Since"), /* @__PURE__ */ React.createElement("div", null, "Expires"), /* @__PURE__ */ React.createElement("div", null, "Score (30d)"), /* @__PURE__ */ React.createElement("div", null, "Status"), /* @__PURE__ */ React.createElement("div", null, "Actions")), /* @__PURE__ */ React.createElement("div", { className: "divide-y divide-white/10" }, filteredEntries.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "px-5 py-10 text-center text-sm text-slate-400" }, "No featured entries match the current filter.") : filteredEntries.map((entry) => /* @__PURE__ */ React.createElement("div", { key: entry.id, className: "grid gap-5 bg-white/[0.02] px-5 py-5 lg:grid-cols-[1.2fr_1fr_0.5fr_0.9fr_0.9fr_0.7fr_1.5fr_0.9fr] lg:items-center" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-[92px_1fr]" }, /* @__PURE__ */ React.createElement("a", { href: entry.artwork?.canonical_url || "#", target: "_blank", rel: "noreferrer", className: "overflow-hidden rounded-2xl border border-white/10 bg-[#08111d]" }, /* @__PURE__ */ React.createElement("img", { src: entry.artwork?.thumbnail?.url, alt: entry.artwork?.title || "Artwork preview", className: "h-24 w-full object-cover" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, entry.artwork?.title || "Missing artwork"), /* @__PURE__ */ React.createElement("span", { className: "text-xs text-slate-400" }, "#", entry.artwork?.id || entry.artwork_id)), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs leading-6 text-slate-400" }, "Visibility: ", entry.artwork?.visibility || "—", " • Published: ", entry.artwork?.published_at ? "Yes" : "No"), entry.is_winner && entry.winner_reason ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs leading-6 text-amber-100" }, entry.winner_reason) : null)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, entry.artwork?.owner?.display_name || "Unknown"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-400" }, entry.artwork?.owner?.type === "group" ? "Group publisher" : `@${entry.artwork?.owner?.username || ""}`)), /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, entry.priority), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-200" }, formatDateTime$3(entry.featured_at)), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-200" }, formatDateTime$3(entry.expires_at)), /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, entry.medals?.score_30d || 0), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (entry.status_badges || []).map((badge, index2) => /* @__PURE__ */ React.createElement(Badge$2, { key: `${entry.id}-${badge.label}-${index2}`, label: badge.label, tone: badge.tone }))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 lg:justify-end" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => editEntry(entry), className: "rounded-full border border-white/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-100 transition hover:border-white/20 hover:bg-white/5" }, "Edit"), capabilities.forceHeroEnabled ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleForceHero(entry), disabled: busy === `force-${entry.id}`, className: `rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] transition disabled:cursor-not-allowed disabled:opacity-60 ${entry.is_force_hero ? "border-amber-300/25 text-amber-100 hover:border-amber-300/40 hover:bg-amber-400/10" : "border-amber-300/15 text-amber-50 hover:border-amber-300/30 hover:bg-amber-400/5"}` }, busy === `force-${entry.id}` ? "Saving…" : entry.is_force_hero ? "Disable Force Hero" : "Force Hero") : null, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleToggle(entry), disabled: busy === `toggle-${entry.id}`, className: "rounded-full border border-sky-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-400/10 disabled:cursor-not-allowed disabled:opacity-60" }, busy === `toggle-${entry.id}` ? "Saving…" : entry.is_active ? "Deactivate" : "Activate"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleDelete(entry), disabled: busy === `delete-${entry.id}`, className: "rounded-full border border-rose-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-rose-100 transition hover:border-rose-300/40 hover:bg-rose-400/10 disabled:cursor-not-allowed disabled:opacity-60" }, busy === `delete-${entry.id}` ? "Deleting…" : "Delete")))))))))); } -const __vite_glob_0_35 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_39 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: FeaturedArtworksAdmin }, Symbol.toStringTag, { value: "Module" })); function AdminFeaturedArtworks() { return /* @__PURE__ */ React.createElement(AdminLayout, null, /* @__PURE__ */ React.createElement(FeaturedArtworksAdmin, null)); } -const __vite_glob_0_15 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_19 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AdminFeaturedArtworks }, Symbol.toStringTag, { value: "Module" })); @@ -18743,7 +22690,7 @@ function LinkFields({ title, prefix, form, options }) { return /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 rounded-[28px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-sm font-semibold uppercase tracking-[0.18em] text-white/75" }, title), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField, { label: "Label", value: form.data[`${prefix}_link_label`], onChange: (event) => form.setData(`${prefix}_link_label`, event.target.value), error: form.errors[`${prefix}_link_label`], maxLength: 80 }), /* @__PURE__ */ React.createElement(SelectField, { label: "Link type", value: form.data[`${prefix}_link_type`], onChange: (nextValue) => form.setData(`${prefix}_link_type`, nextValue), options: options.linkTypes, error: form.errors[`${prefix}_link_type`] })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField, { label: "Fallback URL", value: form.data[`${prefix}_link_url`], onChange: (event) => form.setData(`${prefix}_link_url`, event.target.value), error: form.errors[`${prefix}_link_url`], placeholder: "/explore or https://example.com", maxLength: 2048 }), /* @__PURE__ */ React.createElement(TextField, { label: "Target ID", value: form.data[`${prefix}_link_target_id`], onChange: (event) => form.setData(`${prefix}_link_target_id`, event.target.value), error: form.errors[`${prefix}_link_target_id`], placeholder: "Optional entity id", inputMode: "numeric" }))); } function HomepageAnnouncementForm({ announcement, previewAnnouncement, options, submitUrl, previewUrl, indexUrl, destroyUrl }) { - const { props } = X(); + const { props } = X$1(); const isEditing = Boolean(announcement?.id); const flash = props.flash ?? {}; const [activeTab, setActiveTab] = React.useState("overview"); @@ -18753,7 +22700,7 @@ function HomepageAnnouncementForm({ announcement, previewAnnouncement, options, const [backgroundImageError, setBackgroundImageError] = React.useState(""); const [backgroundPreviewUrl, setBackgroundPreviewUrl] = React.useState(announcement?.background_image_url || ""); const [toast, setToast] = React.useState({ id: 0, visible: false, message: "", variant: "success" }); - const form = G({ + const form = G$1({ ...announcement, background_image_file: null }); @@ -18926,7 +22873,7 @@ function HomepageAnnouncementForm({ announcement, previewAnnouncement, options, setPreviewBusy(false); } }; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: isEditing ? "Edit Homepage Announcement" : "Create Homepage Announcement", subtitle: "Compose, schedule, preview, and publish a premium homepage announcement card." }, /* @__PURE__ */ React.createElement(Se, { title: isEditing ? "Admin · Edit Homepage Announcement" : "Admin · Create Homepage Announcement" }), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: isEditing ? "Edit Homepage Announcement" : "Create Homepage Announcement", subtitle: "Compose, schedule, preview, and publish a premium homepage announcement card." }, /* @__PURE__ */ React.createElement(Se$1, { title: isEditing ? "Admin · Edit Homepage Announcement" : "Admin · Create Homepage Announcement" }), /* @__PURE__ */ React.createElement( ShareToast, { key: toast.id, @@ -18989,7 +22936,7 @@ function HomepageAnnouncementForm({ announcement, previewAnnouncement, options, } }, help: "Turn this on to clear the saved background image on the next save." })) : null, activeTab === "behavior" ? /* @__PURE__ */ React.createElement(Section$1, { title: "Behavior", description: "Dismiss controls let you force a fresh surface when the message materially changes." }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(TextField, { label: "Dismiss version", value: form.data.dismiss_version, onChange: (event) => form.setData("dismiss_version", event.target.value), error: form.errors.dismiss_version, inputMode: "numeric" }), /* @__PURE__ */ React.createElement(SelectField, { label: "Placement", value: form.data.placement, onChange: (nextValue) => form.setData("placement", nextValue), options: options.placements, error: form.errors.placement })), /* @__PURE__ */ React.createElement(ToggleField, { label: "Users can dismiss this card", checked: Boolean(form.data.is_dismissible), onChange: (event) => form.setData("is_dismissible", event.target.checked), help: "When disabled, the card remains visible and no restore pill is shown." })) : null), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6 xl:sticky xl:top-[7.5rem] xl:self-start" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Section$1, { title: "Preview", description: "Refresh the preview to render the sanitized content and resolved CTA payload exactly as the homepage card sees it." }, /* @__PURE__ */ React.createElement("div", { className: "-mx-6 -mt-6 mb-5 border-b border-white/10 bg-slate-950/92 px-6 py-4 backdrop-blur-xl" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: runPreview, disabled: previewBusy, className: "rounded-full border border-sky-300/20 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18 disabled:opacity-60" }, previewBusy ? "Refreshing preview…" : "Refresh preview"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => submit(), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Save changes")), previewError ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-rose-300" }, previewError) : null), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[30px] border border-white/10 bg-black/20 py-2" }, /* @__PURE__ */ React.createElement(HomepageAnnouncement, { announcement: previewWithLocalImage, mode: "preview" }))))))); } -const __vite_glob_0_16 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_20 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: HomepageAnnouncementForm }, Symbol.toStringTag, { value: "Module" })); @@ -19004,9 +22951,9 @@ function StatusBadge({ status: status2, active }) { return /* @__PURE__ */ React.createElement("span", { className: `inline-flex items-center gap-2 rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] ${tone}` }, /* @__PURE__ */ React.createElement("span", { className: `h-2 w-2 rounded-full ${active ? "bg-emerald-300" : "bg-slate-500"}` }), status2); } function HomepageAnnouncementsIndex({ announcements, createUrl }) { - const { props } = X(); + const { props } = X$1(); const flash = props.flash ?? {}; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Homepage Announcements", subtitle: "Schedule launch cards, homepage notices, and editorial announcements below the featured artwork hero." }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Homepage Announcements" }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-2xl text-sm leading-6 text-slate-400" }, "Only the highest-priority published announcement that is active and inside its visibility window appears on the homepage."), /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/20 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18" }, "Create announcement")), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, (announcements?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] px-6 py-10 text-center text-slate-400" }, "No homepage announcements exist yet.") : announcements.data.map((announcement) => /* @__PURE__ */ React.createElement("article", { key: announcement.id, className: "overflow-hidden rounded-[30px] border border-white/10 bg-white/[0.03]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 border-b border-white/8 px-6 py-6 lg:grid-cols-[minmax(0,1.2fr)_auto] lg:items-start" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement(StatusBadge, { status: announcement.status, active: announcement.is_active }), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, announcement.type), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, "Priority ", announcement.priority), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, "Dismiss v", announcement.dismiss_version)), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.04em] text-white" }, announcement.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, formatDateRange(announcement.starts_at, announcement.ends_at)), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, announcement.placement.replaceAll("_", " "))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, /* @__PURE__ */ React.createElement(xe, { href: announcement.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Edit"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Homepage Announcements", subtitle: "Schedule launch cards, homepage notices, and editorial announcements below the featured artwork hero." }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Homepage Announcements" }), flash.success ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100" }, flash.success) : null, flash.error ? /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, flash.error) : null, /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-2xl text-sm leading-6 text-slate-400" }, "Only the highest-priority published announcement that is active and inside its visibility window appears on the homepage."), /* @__PURE__ */ React.createElement(xe, { href: createUrl, className: "rounded-full border border-sky-300/20 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18" }, "Create announcement")), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, (announcements?.data || []).length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] px-6 py-10 text-center text-slate-400" }, "No homepage announcements exist yet.") : announcements.data.map((announcement) => /* @__PURE__ */ React.createElement("article", { key: announcement.id, className: "overflow-hidden rounded-[30px] border border-white/10 bg-white/[0.03]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 border-b border-white/8 px-6 py-6 lg:grid-cols-[minmax(0,1.2fr)_auto] lg:items-start" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement(StatusBadge, { status: announcement.status, active: announcement.is_active }), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, announcement.type), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, "Priority ", announcement.priority), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70" }, "Dismiss v", announcement.dismiss_version)), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.04em] text-white" }, announcement.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, formatDateRange(announcement.starts_at, announcement.ends_at)), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, announcement.placement.replaceAll("_", " "))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 lg:justify-end" }, /* @__PURE__ */ React.createElement(xe, { href: announcement.edit_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Edit"), /* @__PURE__ */ React.createElement( "button", { type: "button", @@ -19028,7 +22975,7 @@ function HomepageAnnouncementsIndex({ announcements, createUrl }) { } ) : /* @__PURE__ */ React.createElement("span", { key: `${link2.label}-${index2}`, className: "rounded-lg px-3 py-1.5 text-xs text-slate-600", dangerouslySetInnerHTML: { __html: link2.label } })))) : null); } -const __vite_glob_0_17 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_21 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: HomepageAnnouncementsIndex }, Symbol.toStringTag, { value: "Module" })); @@ -19050,7 +22997,7 @@ const SETTING_GROUPS = [ } ]; function AdminSettings({ settings = {} }) { - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Settings", subtitle: "Platform-wide configuration" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Settings" }), /* @__PURE__ */ React.createElement("div", { className: "space-y-8 max-w-2xl" }, SETTING_GROUPS.map((group) => /* @__PURE__ */ React.createElement("div", { key: group.label, className: "rounded-2xl border border-white/[0.07] bg-white/[0.02] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "mb-5 text-sm font-bold uppercase tracking-wider text-slate-500" }, group.label), /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, group.items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "flex items-start justify-between gap-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-medium text-white" }, item.label), /* @__PURE__ */ React.createElement("p", { className: "mt-0.5 text-xs text-slate-500" }, item.description)), item.type === "toggle" ? /* @__PURE__ */ React.createElement("div", { className: "flex h-6 w-11 flex-shrink-0 cursor-not-allowed items-center rounded-full border border-white/10 bg-white/[0.06] px-1 opacity-60" }, /* @__PURE__ */ React.createElement("span", { className: "h-4 w-4 rounded-full bg-slate-600" })) : item.type === "textarea" ? /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Settings", subtitle: "Platform-wide configuration" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Settings" }), /* @__PURE__ */ React.createElement("div", { className: "space-y-8 max-w-2xl" }, SETTING_GROUPS.map((group) => /* @__PURE__ */ React.createElement("div", { key: group.label, className: "rounded-2xl border border-white/[0.07] bg-white/[0.02] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "mb-5 text-sm font-bold uppercase tracking-wider text-slate-500" }, group.label), /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, group.items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "flex items-start justify-between gap-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-medium text-white" }, item.label), /* @__PURE__ */ React.createElement("p", { className: "mt-0.5 text-xs text-slate-500" }, item.description)), item.type === "toggle" ? /* @__PURE__ */ React.createElement("div", { className: "flex h-6 w-11 flex-shrink-0 cursor-not-allowed items-center rounded-full border border-white/10 bg-white/[0.06] px-1 opacity-60" }, /* @__PURE__ */ React.createElement("span", { className: "h-4 w-4 rounded-full bg-slate-600" })) : item.type === "textarea" ? /* @__PURE__ */ React.createElement( "textarea", { defaultValue: settings[item.key] ?? "", @@ -19068,13 +23015,13 @@ function AdminSettings({ settings = {} }) { } )))))), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-600" }, "Full settings management via config files and environment variables."))); } -const __vite_glob_0_18 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_22 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AdminSettings }, Symbol.toStringTag, { value: "Module" })); function AdminStories({ stories }) { const items = stories?.data ?? []; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Stories", subtitle: "Review all stories submitted by creators" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Stories" }), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-white/[0.07] bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-600" }, /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Title"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Author"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Status"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Published"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5 text-right" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/[0.04]" }, items.length === 0 && /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: 5, className: "px-5 py-12 text-center text-slate-500" }, "No stories found.")), items.map((story) => /* @__PURE__ */ React.createElement("tr", { key: story.id, className: "transition hover:bg-white/[0.025]" }, /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 font-medium text-white" }, story.title || /* @__PURE__ */ React.createElement("span", { className: "italic text-slate-500" }, "Untitled")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-400" }, story.creator?.name ?? "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("span", { className: `inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${story.status === "published" ? "bg-teal-500/20 text-teal-300" : story.status === "pending_review" ? "bg-amber-500/20 text-amber-300" : "bg-slate-500/20 text-slate-400"}` }, story.status?.replace("_", " ") ?? "draft")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-500" }, story.published_at ? new Date(story.published_at).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-right" }, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Stories", subtitle: "Review all stories submitted by creators" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Stories" }), /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-white/[0.07] bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-600" }, /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Title"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Author"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Status"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5" }, "Published"), /* @__PURE__ */ React.createElement("th", { className: "px-5 py-3.5 text-right" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/[0.04]" }, items.length === 0 && /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: 5, className: "px-5 py-12 text-center text-slate-500" }, "No stories found.")), items.map((story) => /* @__PURE__ */ React.createElement("tr", { key: story.id, className: "transition hover:bg-white/[0.025]" }, /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 font-medium text-white" }, story.title || /* @__PURE__ */ React.createElement("span", { className: "italic text-slate-500" }, "Untitled")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-400" }, story.creator?.name ?? "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4" }, /* @__PURE__ */ React.createElement("span", { className: `inline-flex rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${story.status === "published" ? "bg-teal-500/20 text-teal-300" : story.status === "pending_review" ? "bg-amber-500/20 text-amber-300" : "bg-slate-500/20 text-slate-400"}` }, story.status?.replace("_", " ") ?? "draft")), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-slate-500" }, story.published_at ? new Date(story.published_at).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "—"), /* @__PURE__ */ React.createElement("td", { className: "px-5 py-4 text-right" }, /* @__PURE__ */ React.createElement( "a", { href: `/studio/stories/${story.id}/edit`, @@ -19092,7 +23039,7 @@ function AdminStories({ stories }) { } ) : /* @__PURE__ */ React.createElement("span", { key: i, className: "rounded-lg px-3 py-1.5 text-xs text-slate-700", dangerouslySetInnerHTML: { __html: link2.label } })))))); } -const __vite_glob_0_19 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_23 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AdminStories }, Symbol.toStringTag, { value: "Module" })); @@ -19156,9 +23103,9 @@ function AdminUploadQueue() { )))))))); } function UploadQueuePage() { - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Upload Queue", subtitle: "Review and moderate pending artwork submissions" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Upload Queue" }), /* @__PURE__ */ React.createElement(AdminUploadQueue, null)); + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Upload Queue", subtitle: "Review and moderate pending artwork submissions" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Upload Queue" }), /* @__PURE__ */ React.createElement(AdminUploadQueue, null)); } -const __vite_glob_0_20 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_24 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: UploadQueuePage }, Symbol.toStringTag, { value: "Module" })); @@ -19220,9 +23167,9 @@ function AdminUsernameQueue() { )))))))); } function UsernameQueuePage() { - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Username Queue", subtitle: "Review and approve pending username change requests" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Username Queue" }), /* @__PURE__ */ React.createElement(AdminUsernameQueue, null)); + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Username Queue", subtitle: "Review and approve pending username change requests" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Username Queue" }), /* @__PURE__ */ React.createElement(AdminUsernameQueue, null)); } -const __vite_glob_0_21 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_25 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: UsernameQueuePage }, Symbol.toStringTag, { value: "Module" })); @@ -19279,7 +23226,7 @@ function RoleDropdown({ user, roles, currentUserIsAdmin }) { ))))); } function UsersIndex({ users, filters, roles }) { - const { props } = X(); + const { props } = X$1(); const currentUserIsAdmin = Boolean(props.auth?.user?.is_admin); const flash = props.flash ?? {}; const handleSearch = (e) => { @@ -19290,7 +23237,7 @@ function UsersIndex({ users, filters, roles }) { const handleRoleFilter = (role) => { At.get("/moderation/users", { search: filters.search, role }, { preserveState: true }); }; - return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Users", subtitle: "Search, view and manage user roles" }, /* @__PURE__ */ React.createElement(Se, { title: "Admin · Users" }), flash.success && /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-xl border border-teal-500/20 bg-teal-500/10 px-4 py-3 text-sm text-teal-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-check mr-2" }), flash.success), flash.error && /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-xl border border-rose-500/20 bg-rose-500/10 px-4 py-3 text-sm text-rose-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-exclamation mr-2" }), flash.error), /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "flex flex-1 items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "relative flex-1 max-w-sm" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-xs text-slate-500" }), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(AdminLayout, { title: "Users", subtitle: "Search, view and manage user roles" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Admin · Users" }), flash.success && /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-xl border border-teal-500/20 bg-teal-500/10 px-4 py-3 text-sm text-teal-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-check mr-2" }), flash.success), flash.error && /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-xl border border-rose-500/20 bg-rose-500/10 px-4 py-3 text-sm text-rose-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-exclamation mr-2" }), flash.error), /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "flex flex-1 items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "relative flex-1 max-w-sm" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-xs text-slate-500" }), /* @__PURE__ */ React.createElement( "input", { name: "search", @@ -19318,7 +23265,7 @@ function UsersIndex({ users, filters, roles }) { } ) : /* @__PURE__ */ React.createElement("span", { key: i, className: "rounded-lg px-3 py-1.5 text-xs text-slate-700", dangerouslySetInnerHTML: { __html: link2.label } })))))); } -const __vite_glob_0_22 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_26 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: UsersIndex }, Symbol.toStringTag, { value: "Module" })); @@ -19382,7 +23329,7 @@ function SimilarArtworksHeader({ artwork }) { artwork.content_type_name || "artworks" ))))); } -const __vite_glob_0_23 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_27 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: SimilarArtworksHeader }, Symbol.toStringTag, { value: "Module" })); @@ -21550,12 +25497,12 @@ function requireCjs$2() { } } function match(re2) { - var m = re2.exec(style); - if (!m) return; - var str = m[0]; + var m2 = re2.exec(style); + if (!m2) return; + var str = m2[0]; updatePosition(str); style = style.slice(str.length); - return m; + return m2; } function whitespace2() { match(WHITESPACE_REGEX); @@ -31799,7 +35746,7 @@ function EmojiPickerButton({ onEmojiSelect, disabled = false, className = "" }) { type: "button", disabled, - onClick: () => setOpen((v) => !v), + onClick: () => setOpen((v2) => !v2), "aria-label": "Open emoji picker", "aria-expanded": open, className: [ @@ -31918,7 +35865,7 @@ function CommentForm$1({ const end = el.selectionEnd; const selected = content2.slice(start, end); const lines = selected ? selected.split("\n") : [""]; - const prefixed = lines.map((l) => prefix + l).join("\n"); + const prefixed = lines.map((l3) => prefix + l3).join("\n"); const next = content2.slice(0, start) + prefixed + content2.slice(end); setContent(next); requestAnimationFrame(() => { @@ -31952,7 +35899,7 @@ function CommentForm$1({ const insertAtCursor = reactExports.useCallback((text2) => { const el = textareaRef.current; if (!el) { - setContent((v) => v + text2); + setContent((v2) => v2 + text2); return; } const start = el.selectionStart ?? content2.length; @@ -31968,7 +35915,7 @@ function CommentForm$1({ const handleEmojiSelect = reactExports.useCallback((emoji) => { insertAtCursor(emoji); }, [insertAtCursor]); - const handleKeyDown = reactExports.useCallback((e) => { + const handleKeyDown2 = reactExports.useCallback((e) => { const mod = e.ctrlKey || e.metaKey; if (!mod) return; switch (e.key.toLowerCase()) { @@ -32075,7 +36022,7 @@ function CommentForm$1({ ref: textareaRef, value: content2, onChange: (e) => setContent(e.target.value), - onKeyDown: handleKeyDown, + onKeyDown: handleKeyDown2, placeholder: replyTo ? `Reply to ${replyTo}…` : placeholder, rows: compact ? 2 : 4, maxLength, @@ -32264,7 +36211,7 @@ function ReactionBar({ entityType, entityId, initialTotals = {}, isLoggedIn = fa "button", { type: "button", - onClick: () => setPickerOpen((v) => !v), + onClick: () => setPickerOpen((v2) => !v2), className: summaryClassName, "aria-label": `${totalCount} reaction${totalCount !== 1 ? "s" : ""}` }, @@ -32432,7 +36379,7 @@ function ReplyItem$1({ reply, parentId, artworkId, isLoggedIn, onReplyPosted, de "button", { type: "button", - onClick: () => setShowReplyForm((v) => !v), + onClick: () => setShowReplyForm((v2) => !v2), className: [ "inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[10px] font-medium uppercase tracking-wider transition-all duration-200 focus:outline-none", showReplyForm ? "bg-accent/10 text-accent" : "text-white/35 hover:bg-white/[0.06] hover:text-white/65" @@ -32548,7 +36495,7 @@ function CommentItem$3({ comment, isLoggedIn, artworkId, onReplyPosted }) { "button", { type: "button", - onClick: () => setShowReplyForm((v) => !v), + onClick: () => setShowReplyForm((v2) => !v2), className: [ "inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-medium uppercase tracking-wider transition-all duration-200 focus:outline-none", showReplyForm ? "bg-accent/10 text-accent" : "text-white/40 hover:bg-white/[0.06] hover:text-white/70" @@ -32725,7 +36672,7 @@ function useWebShare({ onFallback } = {}) { ); return { canNativeShare, share }; } -const ArtworkShareModal = reactExports.lazy(() => import("./assets/ArtworkShareModal-BI8kkaqs.js")); +const ArtworkShareModal = reactExports.lazy(() => import("./assets/ArtworkShareModal-BPM8yel5.js")); function ShareIcon() { return /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "h-5 w-5" }, /* @__PURE__ */ React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 1 1 0-2.684m0 2.684 6.632 3.316m-6.632-6 6.632-3.316m0 0a3 3 0 1 0 5.367-2.684 3 3 0 0 0-5.367 2.684Zm0 9.316a3 3 0 1 0 5.368 2.684 3 3 0 0 0-5.368-2.684Z" })); } @@ -33520,13 +37467,13 @@ function NovaConfirmDialog({ }, [open]); reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape" && !busy) { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [busy, onClose, open]); if (!open) return null; const confirmClassName = confirmTone === "danger" ? "border border-rose-400/25 bg-rose-500/12 text-rose-100 hover:bg-rose-500/18 focus-visible:ring-rose-300/50" : "border border-accent/25 bg-accent/90 text-deep hover:brightness-110 focus-visible:ring-accent/50"; @@ -34526,7 +38473,7 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present const mediaItems = [coverItem, ...screenshotItems].filter((item) => Boolean(item.thumbUrl || item.lgUrl || item.xlUrl)); const selectedMedia = mediaItems.find((item) => item.id === selectedMediaId) || mediaItems[0] || null; const initialAwards = artwork?.medals ?? artwork?.awards ?? null; - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo }), heroPreloadHref ? /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo }), heroPreloadHref ? /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement( "link", { "head-key": "artwork-hero-preload", @@ -34620,7 +38567,7 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present } )); } -const __vite_glob_0_24 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_28 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ArtworkPage }, Symbol.toStringTag, { value: "Module" })); @@ -35330,7 +39277,7 @@ function requireReactDomClient_production() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -35400,10 +39347,10 @@ function requireReactDomClient_production() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -35429,8 +39376,8 @@ function requireReactDomClient_production() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -35530,8 +39477,8 @@ function requireReactDomClient_production() { info += describeFiber(workInProgress2, previous2), previous2 = workInProgress2, workInProgress2 = workInProgress2.return; while (workInProgress2); return info; - } catch (x) { - return "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + return "\nError generating stack: " + x2.message + "\n" + x2.stack; } } var hasOwnProperty2 = Object.prototype.hasOwnProperty, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now = Scheduler.unstable_now, getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, LowPriority = Scheduler.unstable_LowPriority, IdlePriority = Scheduler.unstable_IdlePriority, log$1 = Scheduler.log, unstable_setDisableYieldValue = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null; @@ -35544,9 +39491,9 @@ function requireReactDomClient_production() { } } var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log = Math.log, LN2 = Math.LN2; - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } var nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304; function getHighestPriorityLanes(lanes) { @@ -36678,8 +40625,8 @@ function requireReactDomClient_production() { if ("input" === domEventName || "change" === domEventName) return getInstIfValueChanged(targetInst); } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } var objectIs = "function" === typeof Object.is ? Object.is : is; function shallowEqual(objA, objB) { @@ -37479,10 +41426,10 @@ function requireReactDomClient_production() { try { var init2 = lazyType._init; return init2(lazyType._payload); - } catch (x) { - if (null !== x && "object" === typeof x && "function" === typeof x.then) - throw suspendedThenable = x, SuspenseException; - throw x; + } catch (x2) { + if (null !== x2 && "object" === typeof x2 && "function" === typeof x2.then) + throw suspendedThenable = x2, SuspenseException; + throw x2; } } var suspendedThenable = null; @@ -37945,9 +41892,9 @@ function requireReactDomClient_production() { ); thenableState$1 = null; return firstChildFiber; - } catch (x) { - if (x === SuspenseException || x === SuspenseActionException) throw x; - var fiber = createFiberImplClass(29, x, null, returnFiber.mode); + } catch (x2) { + if (x2 === SuspenseException || x2 === SuspenseActionException) throw x2; + var fiber = createFiberImplClass(29, x2, null, returnFiber.mode); fiber.lanes = lanes; fiber.return = returnFiber; return fiber; @@ -38743,9 +42690,9 @@ function requireReactDomClient_production() { if ("object" === typeof currentStateHook && null !== currentStateHook && "function" === typeof currentStateHook.then) try { var state = useThenable(currentStateHook); - } catch (x) { - if (x === SuspenseException) throw SuspenseActionException; - throw x; + } catch (x2) { + if (x2 === SuspenseException) throw SuspenseActionException; + throw x2; } else state = currentStateHook; currentStateHook = updateWorkInProgressHook(); @@ -46936,7 +50883,7 @@ function requireReactDomClient_development() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -47152,10 +51099,10 @@ function requireReactDomClient_development() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -47186,8 +51133,8 @@ function requireReactDomClient_development() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -47319,8 +51266,8 @@ function requireReactDomClient_development() { workInProgress2 = workInProgress2.return; } while (workInProgress2); return info; - } catch (x) { - return "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + return "\nError generating stack: " + x2.message + "\n" + x2.stack; } } function describeFunctionComponentFrameWithoutLineNumber(fn) { @@ -47379,8 +51326,8 @@ function requireReactDomClient_development() { (workInProgress2 = workInProgress2.owner) && ownerStack && (info += "\n" + formatOwnerStack(ownerStack)); } else break; var JSCompiler_inline_result = info; - } catch (x) { - JSCompiler_inline_result = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + JSCompiler_inline_result = "\nError generating stack: " + x2.message + "\n" + x2.stack; } return JSCompiler_inline_result; } @@ -47466,9 +51413,9 @@ function requireReactDomClient_development() { )); } } - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } function getHighestPriorityLanes(lanes) { var pendingSyncLanes = lanes & 42; @@ -48166,7 +52113,7 @@ function requireReactDomClient_development() { return indentation(indent) + describeTextNode(clientText, maxLength) + "\n"; } function objectName(object) { - return Object.prototype.toString.call(object).replace(/^\[object (.*)\]$/, function(m, p0) { + return Object.prototype.toString.call(object).replace(/^\[object (.*)\]$/, function(m2, p0) { return p0; }); } @@ -48367,7 +52314,7 @@ function requireReactDomClient_development() { function describeDiff(rootNode) { try { return "\n\n" + describeNode(rootNode, 0); - } catch (x) { + } catch (x2) { return ""; } } @@ -49227,8 +53174,8 @@ function requireReactDomClient_development() { if ("input" === domEventName || "change" === domEventName) return getInstIfValueChanged(targetInst); } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } function shallowEqual(objA, objB) { if (objectIs(objA, objB)) return true; @@ -50906,10 +54853,10 @@ function requireReactDomClient_development() { function resolveLazy(lazyType) { try { return callLazyInitInDEV(lazyType); - } catch (x) { - if (null !== x && "object" === typeof x && "function" === typeof x.then) - throw suspendedThenable = x, needsToResetSuspendedThenableDEV = true, SuspenseException; - throw x; + } catch (x2) { + if (null !== x2 && "object" === typeof x2 && "function" === typeof x2.then) + throw suspendedThenable = x2, needsToResetSuspendedThenableDEV = true, SuspenseException; + throw x2; } } function getSuspendedThenable() { @@ -51626,9 +55573,9 @@ function requireReactDomClient_development() { ); thenableState$1 = null; return firstChildFiber; - } catch (x) { - if (x === SuspenseException || x === SuspenseActionException) throw x; - var fiber = createFiber(29, x, null, returnFiber.mode); + } catch (x2) { + if (x2 === SuspenseException || x2 === SuspenseActionException) throw x2; + var fiber = createFiber(29, x2, null, returnFiber.mode); fiber.lanes = lanes; fiber.return = returnFiber; var debugInfo = fiber._debugInfo = currentDebugInfo; @@ -52742,9 +56689,9 @@ function requireReactDomClient_development() { if ("object" === typeof currentStateHook && null !== currentStateHook && "function" === typeof currentStateHook.then) try { var state = useThenable(currentStateHook); - } catch (x) { - if (x === SuspenseException) throw SuspenseActionException; - throw x; + } catch (x2) { + if (x2 === SuspenseException) throw SuspenseActionException; + throw x2; } else state = currentStateHook; currentStateHook = updateWorkInProgressHook(); @@ -66970,7 +70917,7 @@ if (typeof document !== "undefined") { clientExports.createRoot(mountElement).render(/* @__PURE__ */ React.createElement(CategoriesPage, { ...props })); } } -const __vite_glob_0_25 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_29 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CategoriesPage }, Symbol.toStringTag, { value: "Module" })); @@ -66983,16 +70930,16 @@ function TimelineChart({ timeline }) { return /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Timeline"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Engagement trend")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, safeTimeline.length, " days")), safeTimeline.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 flex h-64 items-end gap-2 overflow-x-auto rounded-[24px] border border-white/10 bg-slate-950/40 px-4 py-5" }, safeTimeline.map((point2) => /* @__PURE__ */ React.createElement("div", { key: point2.date, className: "flex min-w-[32px] flex-1 flex-col items-center justify-end gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-full w-full items-end gap-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-1/3 rounded-t-full bg-sky-300/80", style: { height: `${Math.max(6, (point2.views || 0) / maxValue * 100)}%` }, title: `Views: ${point2.views || 0}` }), /* @__PURE__ */ React.createElement("div", { className: "w-1/3 rounded-t-full bg-emerald-300/75", style: { height: `${Math.max(6, (point2.likes || 0) / maxValue * 100)}%` }, title: `Likes: ${point2.likes || 0}` }), /* @__PURE__ */ React.createElement("div", { className: "w-1/3 rounded-t-full bg-amber-300/75", style: { height: `${Math.max(6, (point2.saves || 0) / maxValue * 100)}%` }, title: `Saves: ${point2.saves || 0}` })), /* @__PURE__ */ React.createElement("div", { className: "text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500" }, String(point2.date || "").slice(5))))) : /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-12 text-sm text-slate-300" }, "Analytics are enabled, but there are not enough daily snapshots yet to render a timeline."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs font-semibold uppercase tracking-[0.16em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "h-2.5 w-2.5 rounded-full bg-sky-300/80" }), "Views"), /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "h-2.5 w-2.5 rounded-full bg-emerald-300/75" }), "Likes"), /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "h-2.5 w-2.5 rounded-full bg-amber-300/75" }), "Saves"))); } function CollectionAnalytics() { - const { props } = X(); + const { props } = X$1(); const collection = props.collection || {}; const analytics = props.analytics || {}; const totals = analytics.totals || {}; const range2 = analytics.range || {}; const topArtworks = Array.isArray(analytics.top_artworks) ? analytics.top_artworks : []; const seo = props.seo || {}; - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || `${collection.title || "Collection"} Analytics — Skinbase`), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection analytics overview." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[32rem] opacity-95", style: { background: "radial-gradient(circle at 14% 14%, rgba(56,189,248,0.18), transparent 26%), radial-gradient(circle at 86% 18%, rgba(16,185,129,0.16), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, props.dashboardUrl ? /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Dashboard") : null, props.historyUrl ? /* @__PURE__ */ React.createElement("a", { href: props.historyUrl, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-timeline fa-fw text-[11px]" }), "History") : null, collection.manage_url ? /* @__PURE__ */ React.createElement("a", { href: collection.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[11px]" }), "Manage") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Performance"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, collection.title || "Collection analytics"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "Review activity velocity, audience response, and the artworks carrying the most discovery value over the last ", range2.days || 30, " days.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Views", value: totals.views, delta: range2.views_delta, icon: "fa-eye" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Likes", value: totals.likes, delta: range2.likes_delta, icon: "fa-heart" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Follows", value: totals.follows, delta: range2.follows_delta, icon: "fa-bell" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Saves", value: totals.saves, delta: range2.saves_delta, icon: "fa-bookmark" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Comments", value: totals.comments, delta: range2.comments_delta, icon: "fa-comments" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Submissions", value: totals.submissions, delta: totals.submissions, icon: "fa-inbox" })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 space-y-6" }, /* @__PURE__ */ React.createElement(TimelineChart, { timeline: analytics.timeline }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Artworks"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Top artwork drivers")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, topArtworks.length)), topArtworks.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, topArtworks.map((artwork) => /* @__PURE__ */ React.createElement("div", { key: artwork.id, className: "overflow-hidden rounded-[24px] border border-white/10 bg-slate-950/40" }, /* @__PURE__ */ React.createElement("div", { className: "aspect-square bg-slate-950/60" }, artwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full w-full items-center justify-center text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-image text-3xl" }))), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, artwork.title), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-2 text-xs text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Views: ", Number(artwork.views || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Favs: ", Number(artwork.favourites || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Shares: ", Number(artwork.shares || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Rank: ", Number(artwork.ranking_score || 0).toFixed(1))))))) : /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-12 text-sm text-slate-300" }, "Attach or publish more artworks before artwork-level ranking can be surfaced here.")))))); + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || `${collection.title || "Collection"} Analytics — Skinbase`), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection analytics overview." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[32rem] opacity-95", style: { background: "radial-gradient(circle at 14% 14%, rgba(56,189,248,0.18), transparent 26%), radial-gradient(circle at 86% 18%, rgba(16,185,129,0.16), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, props.dashboardUrl ? /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Dashboard") : null, props.historyUrl ? /* @__PURE__ */ React.createElement("a", { href: props.historyUrl, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-timeline fa-fw text-[11px]" }), "History") : null, collection.manage_url ? /* @__PURE__ */ React.createElement("a", { href: collection.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[11px]" }), "Manage") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Performance"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, collection.title || "Collection analytics"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "Review activity velocity, audience response, and the artworks carrying the most discovery value over the last ", range2.days || 30, " days.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Views", value: totals.views, delta: range2.views_delta, icon: "fa-eye" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Likes", value: totals.likes, delta: range2.likes_delta, icon: "fa-heart" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Follows", value: totals.follows, delta: range2.follows_delta, icon: "fa-bell" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Saves", value: totals.saves, delta: range2.saves_delta, icon: "fa-bookmark" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Comments", value: totals.comments, delta: range2.comments_delta, icon: "fa-comments" }), /* @__PURE__ */ React.createElement(MetricCard$1, { label: "Submissions", value: totals.submissions, delta: totals.submissions, icon: "fa-inbox" })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 space-y-6" }, /* @__PURE__ */ React.createElement(TimelineChart, { timeline: analytics.timeline }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Artworks"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Top artwork drivers")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, topArtworks.length)), topArtworks.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, topArtworks.map((artwork) => /* @__PURE__ */ React.createElement("div", { key: artwork.id, className: "overflow-hidden rounded-[24px] border border-white/10 bg-slate-950/40" }, /* @__PURE__ */ React.createElement("div", { className: "aspect-square bg-slate-950/60" }, artwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full w-full items-center justify-center text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-image text-3xl" }))), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, artwork.title), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-2 text-xs text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Views: ", Number(artwork.views || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Favs: ", Number(artwork.favourites || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Shares: ", Number(artwork.shares || 0).toLocaleString()), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Rank: ", Number(artwork.ranking_score || 0).toFixed(1))))))) : /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-12 text-sm text-slate-300" }, "Attach or publish more artworks before artwork-level ranking can be surfaced here.")))))); } -const __vite_glob_0_26 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_30 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionAnalytics }, Symbol.toStringTag, { value: "Module" })); @@ -67330,7 +71277,7 @@ function SearchResults({ state, endpoints, selectedIds, onToggleSelected }) { ))), /* @__PURE__ */ React.createElement(CollectionCard, { collection, isOwner: true }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 text-[11px] font-semibold uppercase tracking-[0.12em] text-slate-400" }, collection.workflow_state ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "Workflow: ", titleize$1(collection.workflow_state)) : null, collection.health_state ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "Health: ", titleize$1(collection.health_state)) : null, collection.placement_eligibility === true ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1 text-emerald-100" }, "Placement Eligible") : null, collection.placement_eligibility === false ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-1 text-rose-100" }, "Placement Blocked") : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, resolve(endpoints?.managePattern, collection.id) ? /* @__PURE__ */ React.createElement("a", { href: resolve(endpoints.managePattern, collection.id), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[10px]" }), "Manage") : null, resolve(endpoints?.analyticsPattern, collection.id) ? /* @__PURE__ */ React.createElement("a", { href: resolve(endpoints.analyticsPattern, collection.id), className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-xs font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column fa-fw text-[10px]" }), "Analytics") : null, resolve(endpoints?.historyPattern, collection.id) ? /* @__PURE__ */ React.createElement("a", { href: resolve(endpoints.historyPattern, collection.id), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-timeline fa-fw text-[10px]" }), "History") : null, resolve(endpoints?.healthPattern, collection.id) ? /* @__PURE__ */ React.createElement("a", { href: resolve(endpoints.healthPattern, collection.id), className: "inline-flex items-center gap-2 rounded-full border border-amber-300/20 bg-amber-400/10 px-4 py-2 text-xs font-semibold text-amber-100 transition hover:bg-amber-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-shield-heart fa-fw text-[10px]" }), "Health") : null))))); } function CollectionDashboard() { - const { props } = X(); + const { props } = X$1(); const [summary, setSummary] = React.useState(props.summary || {}); const topPerforming = Array.isArray(props.topPerforming) ? props.topPerforming : []; const needsAttention = Array.isArray(props.needsAttention) ? props.needsAttention : []; @@ -67443,7 +71390,7 @@ function CollectionDashboard() { setBulkState({ busy: false, error: error.message || "Bulk action failed.", notice: "" }); } } - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collections Dashboard — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection lifecycle and performance dashboard." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[34rem] opacity-95", style: { background: "radial-gradient(circle at 12% 15%, rgba(56,189,248,0.18), transparent 28%), radial-gradient(circle at 84% 14%, rgba(245,158,11,0.16), transparent 26%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Operations"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Collections dashboard"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "A working view of collection health across lifecycle, submissions, quality, and campaign timing. Use it to decide what to publish, repair, archive, or promote next.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Total Collections", value: summary.total ?? 0, icon: "fa-layer-group", tone: "sky" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Drafts", value: summary.drafts ?? 0, icon: "fa-file", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Scheduled", value: summary.scheduled ?? 0, icon: "fa-calendar-days", tone: "emerald" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Published", value: summary.published ?? 0, icon: "fa-globe", tone: "sky" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Archived", value: summary.archived ?? 0, icon: "fa-box-archive", tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Pending Submissions", value: summary.pending_submissions ?? 0, icon: "fa-inbox", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Needs Review", value: summary.needs_review ?? 0, icon: "fa-triangle-exclamation", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Duplicate Risk", value: summary.duplicate_risk ?? 0, icon: "fa-id-card", tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Placement Blocked", value: summary.placement_blocked ?? 0, icon: "fa-ban", tone: "rose" })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_24px_80px_rgba(2,6,23,0.24)] backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Search"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Find the exact collections that need action")), searchState.busy ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-xs font-semibold text-sky-100" }, "Searching...") : null), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "mt-6 grid gap-4 lg:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(SearchField, { label: "Query", value: searchFilters.q, onChange: (event) => updateFilter("q", event.target.value) }, /* @__PURE__ */ React.createElement("input", { value: searchFilters.q, onChange: (event) => updateFilter("q", event.target.value), placeholder: "Title, slug, or campaign", className: "w-full rounded-2xl border border-white/10 bg-[#09111d] px-4 py-3 text-sm text-white outline-none transition placeholder:text-slate-500 focus:border-sky-300/40" })), /* @__PURE__ */ React.createElement(SearchField, { label: "Type" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.type, onChange: (val) => updateFilter("type", val), placeholder: "Any type", options: (Array.isArray(filterOptions.types) ? filterOptions.types : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Visibility" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.visibility, onChange: (val) => updateFilter("visibility", val), placeholder: "Any visibility", options: (Array.isArray(filterOptions.visibilities) ? filterOptions.visibilities : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Lifecycle" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.lifecycle_state, onChange: (val) => updateFilter("lifecycle_state", val), placeholder: "Any lifecycle", options: (Array.isArray(filterOptions.lifecycleStates) ? filterOptions.lifecycleStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Workflow" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.workflow_state, onChange: (val) => updateFilter("workflow_state", val), placeholder: "Any workflow", options: (Array.isArray(filterOptions.workflowStates) ? filterOptions.workflowStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Health" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.health_state, onChange: (val) => updateFilter("health_state", val), placeholder: "Any health state", options: (Array.isArray(filterOptions.healthStates) ? filterOptions.healthStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Placement" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.placement_eligibility, onChange: (val) => updateFilter("placement_eligibility", val), placeholder: "Any placement state", searchable: false, options: [{ value: "1", label: "Eligible" }, { value: "0", label: "Blocked" }] })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end gap-3 xl:col-span-1" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: searchState.busy, className: "inline-flex flex-1 items-center justify-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:cursor-not-allowed disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass fa-fw text-[12px]" }), "Search"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetSearch, disabled: searchState.busy, className: "inline-flex items-center justify-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07] disabled:cursor-not-allowed disabled:opacity-60" }, "Reset"))), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collections Dashboard — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection lifecycle and performance dashboard." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[34rem] opacity-95", style: { background: "radial-gradient(circle at 12% 15%, rgba(56,189,248,0.18), transparent 28%), radial-gradient(circle at 84% 14%, rgba(245,158,11,0.16), transparent 26%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Operations"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Collections dashboard"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "A working view of collection health across lifecycle, submissions, quality, and campaign timing. Use it to decide what to publish, repair, archive, or promote next.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-5 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Total Collections", value: summary.total ?? 0, icon: "fa-layer-group", tone: "sky" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Drafts", value: summary.drafts ?? 0, icon: "fa-file", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Scheduled", value: summary.scheduled ?? 0, icon: "fa-calendar-days", tone: "emerald" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Published", value: summary.published ?? 0, icon: "fa-globe", tone: "sky" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Archived", value: summary.archived ?? 0, icon: "fa-box-archive", tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Pending Submissions", value: summary.pending_submissions ?? 0, icon: "fa-inbox", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Needs Review", value: summary.needs_review ?? 0, icon: "fa-triangle-exclamation", tone: "amber" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Duplicate Risk", value: summary.duplicate_risk ?? 0, icon: "fa-id-card", tone: "rose" }), /* @__PURE__ */ React.createElement(SummaryCard$4, { label: "Placement Blocked", value: summary.placement_blocked ?? 0, icon: "fa-ban", tone: "rose" })), /* @__PURE__ */ React.createElement("div", { className: "mt-8 space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_24px_80px_rgba(2,6,23,0.24)] backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Search"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Find the exact collections that need action")), searchState.busy ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-xs font-semibold text-sky-100" }, "Searching...") : null), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSearch, className: "mt-6 grid gap-4 lg:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(SearchField, { label: "Query", value: searchFilters.q, onChange: (event) => updateFilter("q", event.target.value) }, /* @__PURE__ */ React.createElement("input", { value: searchFilters.q, onChange: (event) => updateFilter("q", event.target.value), placeholder: "Title, slug, or campaign", className: "w-full rounded-2xl border border-white/10 bg-[#09111d] px-4 py-3 text-sm text-white outline-none transition placeholder:text-slate-500 focus:border-sky-300/40" })), /* @__PURE__ */ React.createElement(SearchField, { label: "Type" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.type, onChange: (val) => updateFilter("type", val), placeholder: "Any type", options: (Array.isArray(filterOptions.types) ? filterOptions.types : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Visibility" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.visibility, onChange: (val) => updateFilter("visibility", val), placeholder: "Any visibility", options: (Array.isArray(filterOptions.visibilities) ? filterOptions.visibilities : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Lifecycle" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.lifecycle_state, onChange: (val) => updateFilter("lifecycle_state", val), placeholder: "Any lifecycle", options: (Array.isArray(filterOptions.lifecycleStates) ? filterOptions.lifecycleStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Workflow" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.workflow_state, onChange: (val) => updateFilter("workflow_state", val), placeholder: "Any workflow", options: (Array.isArray(filterOptions.workflowStates) ? filterOptions.workflowStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Health" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.health_state, onChange: (val) => updateFilter("health_state", val), placeholder: "Any health state", options: (Array.isArray(filterOptions.healthStates) ? filterOptions.healthStates : []).map((o) => ({ value: o, label: titleize$1(o) })) })), /* @__PURE__ */ React.createElement(SearchField, { label: "Placement" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: searchFilters.placement_eligibility, onChange: (val) => updateFilter("placement_eligibility", val), placeholder: "Any placement state", searchable: false, options: [{ value: "1", label: "Eligible" }, { value: "0", label: "Blocked" }] })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end gap-3 xl:col-span-1" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: searchState.busy, className: "inline-flex flex-1 items-center justify-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:cursor-not-allowed disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass fa-fw text-[12px]" }), "Search"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetSearch, disabled: searchState.busy, className: "inline-flex items-center justify-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07] disabled:cursor-not-allowed disabled:opacity-60" }, "Reset"))), /* @__PURE__ */ React.createElement( BulkActionsPanel, { selectedCount: selectedIds.length, @@ -67459,7 +71406,7 @@ function CollectionDashboard() { } ), /* @__PURE__ */ React.createElement(SearchResults, { state: searchState, endpoints, selectedIds, onToggleSelected: toggleSelected })), /* @__PURE__ */ React.createElement(WarningList, { warnings: healthWarnings, endpoints }), /* @__PURE__ */ React.createElement(CollectionStrip, { title: "Top Performing", eyebrow: "Momentum", collections: topPerforming, emptyLabel: "No collections have enough activity yet to rank here.", endpoints }), /* @__PURE__ */ React.createElement(CollectionStrip, { title: "Needs Attention", eyebrow: "Quality", collections: needsAttention, emptyLabel: "No collections currently need manual intervention.", endpoints }), /* @__PURE__ */ React.createElement(CollectionStrip, { title: "Expiring Campaigns", eyebrow: "Timing", collections: expiringCampaigns, emptyLabel: "No campaigns are approaching their sunset window.", endpoints }))))); } -const __vite_glob_0_27 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_31 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionDashboard }, Symbol.toStringTag, { value: "Module" })); @@ -67602,15 +71549,15 @@ function SearchPanel({ search: search2 }) { function handleSubmit(event) { event.preventDefault(); const params = {}; - Object.entries(localFilters).forEach(([k2, v]) => { - if (v) params[k2] = v; + Object.entries(localFilters).forEach(([k2, v2]) => { + if (v2) params[k2] = v2; }); At.get("/collections/search", params); } return /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Filters"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Search collections")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, search2?.meta?.total ?? 0, " results")), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("input", { value: localFilters.q, onChange: (e) => updateFilter("q", e.target.value), placeholder: "Search title or summary", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35 xl:col-span-2" }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.type, onChange: (val) => updateFilter("type", val), placeholder: "All types", searchable: false, options: [{ value: "personal", label: "Personal" }, { value: "community", label: "Community" }, { value: "editorial", label: "Editorial" }] }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.sort, onChange: (val) => updateFilter("sort", val), searchable: false, options: [{ value: "trending", label: "Trending" }, { value: "recent", label: "Recent" }, { value: "quality", label: "Quality" }, { value: "evergreen", label: "Evergreen" }] }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.category, onChange: (val) => updateFilter("category", val), placeholder: "Any category", options: (options.category || []).map((item) => ({ value: item.value, label: item.label })) }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.mode, onChange: (val) => updateFilter("mode", val), placeholder: "Any curation mode", searchable: false, options: [{ value: "manual", label: "Manual" }, { value: "smart", label: "Smart" }] }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.style, onChange: (val) => updateFilter("style", val), placeholder: "Any style signal", options: (options.style || []).map((item) => ({ value: item.value, label: item.label })) }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.lifecycle_state, onChange: (val) => updateFilter("lifecycle_state", val), placeholder: "Any lifecycle", searchable: false, options: [{ value: "published", label: "Published" }, { value: "featured", label: "Featured" }, { value: "archived", label: "Archived" }] }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.theme, onChange: (val) => updateFilter("theme", val), placeholder: "Any theme", options: (options.theme || []).map((item) => ({ value: item.value, label: item.label })) }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.health_state, onChange: (val) => updateFilter("health_state", val), placeholder: "Any quality state", searchable: false, options: [{ value: "healthy", label: "Healthy" }, { value: "needs_metadata", label: "Needs metadata" }, { value: "stale", label: "Stale" }, { value: "low_content", label: "Low content" }, { value: "broken_items", label: "Broken items" }, { value: "weak_cover", label: "Weak cover" }, { value: "low_engagement", label: "Low engagement" }, { value: "duplicate_risk", label: "Duplicate risk" }, { value: "merge_candidate", label: "Merge candidate" }] }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.color, onChange: (val) => updateFilter("color", val), placeholder: "Any color palette", options: (options.color || []).map((item) => ({ value: item.value, label: item.label })) }), /* @__PURE__ */ React.createElement("input", { value: localFilters.campaign_key, onChange: (e) => updateFilter("campaign_key", e.target.value), placeholder: "Campaign key", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35" }), /* @__PURE__ */ React.createElement("input", { value: localFilters.program_key, onChange: (e) => updateFilter("program_key", e.target.value), placeholder: "Program key", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35" }), /* @__PURE__ */ React.createElement(NovaSelect, { value: localFilters.quality_tier, onChange: (val) => updateFilter("quality_tier", val), placeholder: "Any quality tier", options: (options.quality_tier || []).map((item) => ({ value: item.value, label: item.label })) }), /* @__PURE__ */ React.createElement("div", { className: "md:col-span-2 xl:col-span-4 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-magnifying-glass fa-fw" }), "Apply filters"), /* @__PURE__ */ React.createElement("a", { href: "/collections/search", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Reset"))), chips.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3" }, chips.map((chip) => /* @__PURE__ */ React.createElement("a", { key: chip.key, href: chip.href, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-3 py-1.5 text-xs font-semibold text-slate-200 transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("span", null, chip.label), /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-xmark text-[10px] text-slate-400" })))) : null, search2?.links?.prev || search2?.links?.next ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3 text-sm" }, search2.links.prev ? /* @__PURE__ */ React.createElement("a", { href: search2.links.prev, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[10px]" }), "Previous") : null, search2.links.next ? /* @__PURE__ */ React.createElement("a", { href: search2.links.next, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white transition hover:bg-white/[0.07]" }, "Next", /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right fa-fw text-[10px]" })) : null) : null); } function CollectionFeaturedIndex() { - const { props } = X(); + const { props } = X$1(); const seo = props.seo || {}; const eyebrow = props.eyebrow || "Discovery"; const title = props.title || "Featured collections"; @@ -67655,7 +71602,7 @@ function CollectionFeaturedIndex() { } ), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("a", { href: "/", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Back to home"), /* @__PURE__ */ React.createElement("a", { href: "/collections/featured", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Featured"), /* @__PURE__ */ React.createElement("a", { href: "/collections/trending", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Trending"), /* @__PURE__ */ React.createElement("a", { href: "/collections/community", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Community"), /* @__PURE__ */ React.createElement("a", { href: "/collections/editorial", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Editorial"), /* @__PURE__ */ React.createElement("a", { href: "/collections/seasonal", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Seasonal")), /* @__PURE__ */ React.createElement("section", { className: "mt-6 overflow-hidden rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.18fr)_400px] xl:items-end" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, eyebrow), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, title), campaign?.badge_label ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 inline-flex items-center rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, campaign.badge_label) : program?.promotion_tier ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 inline-flex items-center rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Promotion tier: ", program.promotion_tier) : null, /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, description), campaign ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3 text-xs text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Campaign key: ", campaign.key), campaign.event_label ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Event: ", campaign.event_label) : null, campaign.season_key ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Season: ", campaign.season_key) : null, Array.isArray(campaign.active_surface_keys) && campaign.active_surface_keys.length ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Surfaces: ", campaign.active_surface_keys.join(", ")) : null) : program ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3 text-xs text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Program key: ", program.key), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Collections: ", program.collections_count ?? collections.length), program.trust_tier ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Trust: ", program.trust_tier) : null, Array.isArray(program.partner_labels) && program.partner_labels.length ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Partners: ", program.partner_labels.join(", ")) : null, Array.isArray(program.sponsorship_labels) && program.sponsorship_labels.length ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-2" }, "Sponsors: ", program.sponsorship_labels.join(", ")) : null) : null), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3 xl:grid-cols-1" }, /* @__PURE__ */ React.createElement(HeroStat$2, { icon: "fa-layer-group", label: "Collections", value: collections.length.toLocaleString() }), /* @__PURE__ */ React.createElement(HeroStat$2, { icon: "fa-wand-magic-sparkles", label: "Smart", value: smartCount.toLocaleString() }), /* @__PURE__ */ React.createElement(HeroStat$2, { icon: "fa-images", label: "Artworks", value: totalArtworks.toLocaleString() })))), /* @__PURE__ */ React.createElement("section", { className: "mt-8" }, /* @__PURE__ */ React.createElement(SearchPanel, { search: search2 })), /* @__PURE__ */ React.createElement("section", { className: "mt-8" }, collections.length ? /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, collections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: mainSave.context, saveContextMeta: mainSave.meta }))) : /* @__PURE__ */ React.createElement(EmptyState$4, null)), communityCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Community"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Collaborative picks"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, communityCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: "community_row", saveContextMeta: { surface_label: "community collections" } })))) : null, trendingCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Trending"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Momentum right now")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, trendingCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: "trending_row", saveContextMeta: { surface_label: "trending collections" } })))) : null, editorialCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Editorial"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Staff and campaign collections")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, editorialCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: "editorial_row", saveContextMeta: { surface_label: "editorial collections" } })))) : null, seasonalCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Seasonal"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Campaign and event spotlights")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, seasonalCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: "seasonal_row", saveContextMeta: { surface_label: "seasonal collections" } })))) : null, recentCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-10 pb-8" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Recent"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Freshly published collections")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, recentCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false, saveContext: "recent_row", saveContextMeta: { surface_label: "recent collections" } })))) : null))); } -const __vite_glob_0_28 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_32 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionFeaturedIndex }, Symbol.toStringTag, { value: "Module" })); @@ -67681,7 +71628,7 @@ function buildPageUrl(pageNumber) { return url.toString(); } function CollectionHistory() { - const { props } = X(); + const { props } = X$1(); const collection = props.collection || {}; const history2 = props.history || {}; const entries = Array.isArray(history2.data) ? history2.data : []; @@ -67714,7 +71661,7 @@ function CollectionHistory() { setBusyId(null); } } - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || `${collection.title || "Collection"} History — Skinbase`), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection audit history." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[32rem] opacity-95", style: { background: "radial-gradient(circle at 14% 14%, rgba(56,189,248,0.16), transparent 26%), radial-gradient(circle at 84% 20%, rgba(244,63,94,0.14), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, props.dashboardUrl ? /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Dashboard") : null, props.analyticsUrl ? /* @__PURE__ */ React.createElement("a", { href: props.analyticsUrl, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column fa-fw text-[11px]" }), "Analytics") : null, collection.manage_url ? /* @__PURE__ */ React.createElement("a", { href: collection.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[11px]" }), "Manage") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Audit"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, collection.title || "Collection history"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "A chronological log of lifecycle transitions, editorial changes, artwork operations, and moderation-adjacent actions for this collection.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 space-y-4" }, notice ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-rose-300/20 bg-rose-500/10 px-5 py-4 text-sm text-rose-100" }, notice) : null, entries.length ? entries.map((entry) => /* @__PURE__ */ React.createElement("article", { key: entry.id, className: "rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, String(entry.action_type || "updated").replace(/_/g, " ")), entry.actor?.username ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "@", entry.actor.username) : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "System"), entry.can_restore ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-emerald-100" }, "Restorable") : null), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-xl font-semibold text-white" }, entry.summary || "Collection updated"), entry.can_restore && Array.isArray(entry.restore_fields) && entry.restore_fields.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs uppercase tracking-[0.18em] text-slate-400" }, "Restores: ", entry.restore_fields.join(", ")) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-end gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, formatDateTime$2(entry.created_at)), props.canRestoreHistory && entry.can_restore ? /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || `${collection.title || "Collection"} History — Skinbase`), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Collection audit history." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[32rem] opacity-95", style: { background: "radial-gradient(circle at 14% 14%, rgba(56,189,248,0.16), transparent 26%), radial-gradient(circle at 84% 20%, rgba(244,63,94,0.14), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, props.dashboardUrl ? /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Dashboard") : null, props.analyticsUrl ? /* @__PURE__ */ React.createElement("a", { href: props.analyticsUrl, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column fa-fw text-[11px]" }), "Analytics") : null, collection.manage_url ? /* @__PURE__ */ React.createElement("a", { href: collection.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[11px]" }), "Manage") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Audit"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, collection.title || "Collection history"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "A chronological log of lifecycle transitions, editorial changes, artwork operations, and moderation-adjacent actions for this collection.")), /* @__PURE__ */ React.createElement("section", { className: "mt-8 space-y-4" }, notice ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-rose-300/20 bg-rose-500/10 px-5 py-4 text-sm text-rose-100" }, notice) : null, entries.length ? entries.map((entry) => /* @__PURE__ */ React.createElement("article", { key: entry.id, className: "rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, String(entry.action_type || "updated").replace(/_/g, " ")), entry.actor?.username ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "@", entry.actor.username) : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "System"), entry.can_restore ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-emerald-100" }, "Restorable") : null), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-xl font-semibold text-white" }, entry.summary || "Collection updated"), entry.can_restore && Array.isArray(entry.restore_fields) && entry.restore_fields.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs uppercase tracking-[0.18em] text-slate-400" }, "Restores: ", entry.restore_fields.join(", ")) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-end gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, formatDateTime$2(entry.created_at)), props.canRestoreHistory && entry.can_restore ? /* @__PURE__ */ React.createElement( "button", { type: "button", @@ -67726,7 +71673,7 @@ function CollectionHistory() { busyId === entry.id ? "Restoring…" : "Restore" ) : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement(FieldChanges, { label: "Before", value: entry.before }), /* @__PURE__ */ React.createElement(FieldChanges, { label: "After", value: entry.after })))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-14 text-sm text-slate-300" }, "No audit entries have been recorded for this collection yet.")), Number(meta.last_page || 1) > 1 ? /* @__PURE__ */ React.createElement("div", { className: "mt-8 flex flex-wrap items-center justify-between gap-3 rounded-[28px] border border-white/10 bg-white/[0.04] px-5 py-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", null, "Page ", meta.current_page || 1, " of ", meta.last_page || 1), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, (meta.current_page || 1) > 1 ? /* @__PURE__ */ React.createElement("a", { href: buildPageUrl((meta.current_page || 1) - 1), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[10px]" }), "Previous") : null, (meta.current_page || 1) < (meta.last_page || 1) ? /* @__PURE__ */ React.createElement("a", { href: buildPageUrl((meta.current_page || 1) + 1), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 font-semibold text-white transition hover:bg-white/[0.07]" }, "Next", /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right fa-fw text-[10px]" })) : null)) : null))); } -const __vite_glob_0_29 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_33 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionHistory }, Symbol.toStringTag, { value: "Module" })); @@ -68252,7 +72199,7 @@ function SmartRuleRow({ ))), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs text-slate-400" }, buildRuleSummary(rule, smartRuleOptions))); } function CollectionManage() { - const { props } = X(); + const { props } = X$1(); const { mode, collection, @@ -69119,7 +73066,7 @@ function CollectionManage() { setMembers(payload?.members || []); setNotice("Collaborator removed by moderation action."); } - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, mode === "create" ? "Create Collection — Skinbase" : `${collectionState?.title || "Collection"} — Manage Collection`), /* @__PURE__ */ React.createElement("meta", { name: "robots", content: "noindex,nofollow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, mode === "create" ? "Create Collection — Skinbase" : `${collectionState?.title || "Collection"} — Manage Collection`), /* @__PURE__ */ React.createElement("meta", { name: "robots", content: "noindex,nofollow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement( "div", { "aria-hidden": "true", @@ -69686,7 +73633,7 @@ function CollectionManage() { } )), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("span", null, "Allow comments"), /* @__PURE__ */ React.createElement(Checkbox, { checked: form.allow_comments, onChange: (event) => handleModerationToggle("allow_comments", event.target.checked) })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("span", null, "Allow submissions"), /* @__PURE__ */ React.createElement(Checkbox, { checked: form.allow_submissions, onChange: (event) => handleModerationToggle("allow_submissions", event.target.checked) })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("span", null, "Allow saves"), /* @__PURE__ */ React.createElement(Checkbox, { checked: form.allow_saves, onChange: (event) => handleModerationToggle("allow_saves", event.target.checked) })))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.04] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Rapid actions"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleAdminUnfeature, className: "rounded-2xl border border-amber-300/25 bg-amber-300/10 px-4 py-3 text-sm font-semibold text-amber-100 transition hover:bg-amber-300/15" }, "Remove featured placement"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleModerationStatusChange("under_review"), className: "rounded-2xl border border-white/12 bg-white/[0.05] px-4 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Send to review"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleModerationStatusChange("restricted"), className: "rounded-2xl border border-rose-400/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/15" }, "Restrict public access")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-4 text-sm leading-relaxed text-slate-300" }, "Current state: ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, (collectionState?.moderation_status || "active").replace("_", " ")))))) : null))); } -const __vite_glob_0_30 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_34 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionManage }, Symbol.toStringTag, { value: "Module" })); @@ -69694,7 +73641,7 @@ function StatCard$6({ icon, label, value }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.05] px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon} text-[10px]` }), label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold tracking-[-0.03em] text-white" }, value)); } function CollectionSeriesShow() { - const { props } = X(); + const { props } = X$1(); const seo = props.seo || {}; const title = props.title || `Collection Series: ${props.seriesKey || ""}`; const description = props.description || "A connected sequence of public collections on Skinbase."; @@ -69703,7 +73650,7 @@ function CollectionSeriesShow() { const stats = props.stats || {}; return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo, title: seo.title || `${title} — Skinbase`, description: seo.description || description }), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[36rem] opacity-95", style: { background: "radial-gradient(circle at 10% 15%, rgba(59,130,246,0.18), transparent 28%), radial-gradient(circle at 84% 18%, rgba(34,197,94,0.16), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("a", { href: "/collections/featured", className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Back to collections"), leadCollection?.url ? /* @__PURE__ */ React.createElement("a", { href: leadCollection.url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, "Lead collection") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 overflow-hidden rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.15fr)_400px] xl:items-end" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Series"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, description), props.seriesKey ? /* @__PURE__ */ React.createElement("div", { className: "mt-5 inline-flex rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-300" }, props.seriesKey) : null), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-3 xl:grid-cols-1" }, /* @__PURE__ */ React.createElement(StatCard$6, { icon: "fa-layer-group", label: "Collections", value: Number(stats.collections || collections.length).toLocaleString() }), /* @__PURE__ */ React.createElement(StatCard$6, { icon: "fa-user-group", label: "Creators", value: Number(stats.owners || 0).toLocaleString() }), /* @__PURE__ */ React.createElement(StatCard$6, { icon: "fa-images", label: "Artworks", value: Number(stats.artworks || 0).toLocaleString() })))), leadCollection ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Lead Entry"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Start with the opening collection")), stats.latest_activity_at ? /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, "Latest activity ", new Date(stats.latest_activity_at).toLocaleDateString()) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 max-w-xl" }, /* @__PURE__ */ React.createElement(CollectionCard, { collection: leadCollection, isOwner: false }))) : null, /* @__PURE__ */ React.createElement("section", { className: "mt-8 pb-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Sequence"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Public collections in order"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3" }, collections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false }))))))); } -const __vite_glob_0_31 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_35 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionSeriesShow }, Symbol.toStringTag, { value: "Module" })); @@ -69933,13 +73880,13 @@ function CollectionPickerModal({ }) { reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose(); } }; - document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); + document.addEventListener("keydown", handleKeyDown2); + return () => document.removeEventListener("keydown", handleKeyDown2); }, [open, onClose]); if (!open) { return null; @@ -69999,7 +73946,7 @@ function ArtworkCard$1({ }) { let inertiaProps = {}; try { - inertiaProps = X()?.props || {}; + inertiaProps = X$1()?.props || {}; } catch { inertiaProps = {}; } @@ -70645,7 +74592,7 @@ function CommentForm({ placeholder = "Write a comment…", submitLabel = "Post", element2.focus(); }); }, [content2]); - const handleKeyDown = reactExports.useCallback((event) => { + const handleKeyDown2 = reactExports.useCallback((event) => { const mod = event.ctrlKey || event.metaKey; if (!mod) return; switch (event.key.toLowerCase()) { @@ -70720,7 +74667,7 @@ function CommentForm({ placeholder = "Write a comment…", submitLabel = "Post", ref: textareaRef, value: content2, onChange: (event) => setContent(event.target.value), - onKeyDown: handleKeyDown, + onKeyDown: handleKeyDown2, rows: compact ? 3 : 4, maxLength: 1e4, placeholder, @@ -71018,7 +74965,7 @@ function ContextSignalCard({ item }) { return /* @__PURE__ */ React.createElement("div", { className: wrapperClassName }, body2); } function CollectionShow() { - const { props } = X(); + const { props } = X$1(); const { collection: initialCollection, artworks, @@ -71413,7 +75360,7 @@ function CollectionShow() { const renderedSidebarModules = enabledModules.filter((module) => module.slot === "sidebar").map(renderModule).filter(Boolean); return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo, title: metaTitle, description: metaDescription, jsonLd: collectionSchema }), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[36rem] opacity-95", style: { background: "radial-gradient(circle at top left, rgba(56,189,248,0.18), transparent 32%), radial-gradient(circle at 82% 10%, rgba(249,115,22,0.18), transparent 26%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("a", { href: profileCollectionsUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left fa-fw text-[11px]" }), "Back to collections"), isOwner && manageUrl ? /* @__PURE__ */ React.createElement("a", { href: manageUrl, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-grip fa-fw text-[11px]" }), "Manage artworks") : null, isOwner && editUrl ? /* @__PURE__ */ React.createElement("a", { href: editUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[11px]" }), "Edit details") : null, isOwner && analyticsUrl ? /* @__PURE__ */ React.createElement("a", { href: analyticsUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column fa-fw text-[11px]" }), "Analytics") : null, isOwner && historyUrl ? /* @__PURE__ */ React.createElement("a", { href: historyUrl, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-timeline fa-fw text-[11px]" }), "History") : null), /* @__PURE__ */ React.createElement("section", { className: "mt-6 overflow-hidden rounded-[34px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] shadow-[0_30px_90px_rgba(2,6,23,0.32)] backdrop-blur-xl" }, /* @__PURE__ */ React.createElement("div", { className: `h-[3px] bg-gradient-to-r ${collection?.type === "editorial" ? "from-amber-400/80 via-amber-400/30 to-transparent" : collection?.type === "community" ? "from-emerald-400/80 via-emerald-400/30 to-transparent" : collection?.mode === "smart" ? "from-sky-400/80 via-sky-400/30 to-transparent" : "from-violet-400/80 via-violet-400/30 to-transparent"}` }), /* @__PURE__ */ React.createElement("div", { className: "grid items-start gap-6 p-5 md:p-7 xl:grid-cols-[minmax(0,1.2fr)_420px]" }, /* @__PURE__ */ React.createElement("div", { className: "relative self-start overflow-hidden rounded-[28px] border border-white/10 bg-slate-950/60" }, /* @__PURE__ */ React.createElement(CollectionCover, { collection }), /* @__PURE__ */ React.createElement("div", { className: "pointer-events-none absolute inset-0 bg-[linear-gradient(to_top,rgba(2,6,23,0.8),rgba(2,6,23,0.08))]" })), /* @__PURE__ */ React.createElement("div", { className: "relative overflow-hidden rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.12),transparent_28%),radial-gradient(circle_at_90%_8%,rgba(251,191,36,0.14),transparent_24%),linear-gradient(180deg,rgba(15,23,42,0.94),rgba(10,18,32,0.92))] px-5 py-6 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] md:px-6 md:py-7" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute -left-14 top-10 h-36 w-36 rounded-full bg-sky-400/10 blur-3xl" }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute -right-10 bottom-8 h-32 w-32 rounded-full bg-amber-300/10 blur-3xl" }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 flex h-full flex-col justify-between" }, collection?.banner_text ? /* @__PURE__ */ React.createElement("div", { className: `mb-4 inline-flex max-w-full items-center gap-2 rounded-[22px] border px-4 py-3 text-sm font-medium shadow-[0_18px_40px_rgba(2,6,23,0.2)] ${spotlightClasses}` }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-sparkles text-[12px]" }), /* @__PURE__ */ React.createElement("span", { className: "truncate" }, collection.banner_text)) : null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, collection?.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/25 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Featured Collection") : null, collection?.mode === "smart" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Smart Collection") : null, /* @__PURE__ */ React.createElement(TypeBadge, { collection }), collection?.event_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white" }, collection.event_label) : null, collection?.campaign_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, collection.campaign_label) : null, collection?.badge_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white" }, collection.badge_label) : null, collection?.series_key ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white" }, "Series ", collection.series_order ? `#${collection.series_order}` : "") : null, isOwner ? /* @__PURE__ */ React.createElement(CollectionVisibilityBadge, { visibility: collection?.visibility }) : null), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 max-w-3xl text-4xl font-black tracking-[-0.06em] text-white md:text-5xl xl:text-[4rem] xl:leading-[0.92]" }, collection?.title), showIntroBlock ? /* @__PURE__ */ React.createElement(React.Fragment, null, collection?.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-lg text-slate-300 md:text-xl" }, collection.subtitle) : null, collection?.summary || collection?.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-2xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, collection?.summary || collection?.description) : /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-2xl text-sm leading-relaxed text-slate-400 md:text-[15px]" }, "A curated selection from @", owner?.username, ", assembled as a focused gallery rather than a simple archive."), collection?.smart_summary ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 max-w-2xl rounded-[22px] border border-sky-300/15 bg-sky-400/[0.07] px-4 py-3 text-sm leading-relaxed text-sky-100/90" }, collection.smart_summary) : null, featuringCreatorsCount > 1 ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, "Featuring artworks by ", featuringCreatorsCount, " creators.") : null) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-7 space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(CollectionHeroAction, { onClick: handleLike, disabled: state.busy || !engagement?.like_url, icon: "fa-heart", label: state.liked ? "Liked" : "Like", tone: "rose", active: state.liked }), /* @__PURE__ */ React.createElement(CollectionHeroAction, { onClick: handleFollow, disabled: state.busy || !engagement?.follow_url, icon: "fa-bell", label: state.following ? "Following" : "Follow", tone: "emerald", active: state.following }), /* @__PURE__ */ React.createElement(CollectionHeroAction, { onClick: handleSave, disabled: state.busy || !engagement?.save_url && !engagement?.unsave_url, icon: "fa-bookmark", label: state.saved ? "Saved" : "Save", tone: "violet", active: state.saved })), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(CollectionHeroAction, { onClick: handleShare, icon: "fa-share-nodes", label: "Share", tone: "neutral", compact: true }), featuredCollectionsUrl ? /* @__PURE__ */ React.createElement(CollectionHeroAction, { href: featuredCollectionsUrl, icon: "fa-compass", label: "Explore", tone: "sky", compact: true }) : null, reportEndpoint && !isOwner ? /* @__PURE__ */ React.createElement(CollectionHeroAction, { onClick: () => handleReport("collection", collection?.id), icon: "fa-flag", label: "Report", tone: "amber", compact: true }) : null)), state.notice ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-sky-100" }, state.notice) : null, /* @__PURE__ */ React.createElement(OwnerCard, { owner, collectionType: collection?.type }))))), heroMetrics.length || heroSignals.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[30px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-5 shadow-[0_20px_70px_rgba(2,6,23,0.22)] backdrop-blur-xl md:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Collection Snapshot"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Stats and placement signals")), /* @__PURE__ */ React.createElement("p", { className: "max-w-xl text-sm leading-relaxed text-slate-400" }, "The engagement counters and ranking signals now live outside the hero so the header can stay focused on the artwork, title, and actions.")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 xl:grid-cols-[minmax(0,1.6fr)_minmax(320px,0.95fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-5" }, heroMetrics.map((item) => /* @__PURE__ */ React.createElement(HeroMetricCard, { key: item.label, icon: item.icon, label: item.label, value: item.value, helper: item.helper, tone: item.tone }))), heroSignals.length ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-1" }, heroSignals.map((item) => /* @__PURE__ */ React.createElement(HeroSignalCard, { key: item.label, icon: item.icon, label: item.label, value: item.value, description: item.description, tone: item.tone }))) : null)) : null, seriesContext?.url || seriesContext?.previous || seriesContext?.next || Array.isArray(seriesContext?.siblings) && seriesContext.siblings.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Series"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, seriesContext?.title || "Connected collection sequence"), seriesContext?.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-relaxed text-slate-300" }, seriesContext.description) : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, collection?.series_key ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, collection.series_key) : null, seriesContext?.url ? /* @__PURE__ */ React.createElement("a", { href: seriesContext.url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-list fa-fw text-[10px]" }), "View full series") : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, seriesContext?.previous ? /* @__PURE__ */ React.createElement("a", { href: seriesContext.previous.url, className: "flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.04] px-5 py-4 transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Previous"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, seriesContext.previous.title)), /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left text-slate-500" })) : null, seriesContext?.next ? /* @__PURE__ */ React.createElement("a", { href: seriesContext.next.url, className: "flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.04] px-5 py-4 transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Next"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, seriesContext.next.title)), /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right text-slate-500" })) : null), Array.isArray(seriesContext?.siblings) && seriesContext.siblings.length ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, seriesContext.siblings.slice(0, 2).map((item) => /* @__PURE__ */ React.createElement(CollectionCard, { key: item.id, collection: item, isOwner: false }))) : null)) : null, contextSignals.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-amber-300/15 bg-amber-400/10 text-amber-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-diagram-project text-sm" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Related Context"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-semibold text-white" }, "Campaign, event, and quality context"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, contextSignals.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, contextSignals.map((item) => /* @__PURE__ */ React.createElement(ContextSignalCard, { key: `${item.meta}-${item.title}`, item })))) : null, storyLinks.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-lime-300/15 bg-lime-400/10 text-lime-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-book-open text-sm" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-lime-200/80" }, "Stories"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-semibold text-white" }, "Stories and editorial references linked to this collection"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, storyLinks.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, storyLinks.map((item) => /* @__PURE__ */ React.createElement(EntityLinkCard, { key: `${item.linked_type}-${item.linked_id}-${item.id}`, item })))) : null, taxonomyLinks.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-violet-300/15 bg-violet-400/10 text-violet-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-tags text-sm" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-violet-200/80" }, "Browse The Theme"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-semibold text-white" }, "Categories and tags that anchor this collection"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, taxonomyLinks.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, taxonomyLinks.map((item) => /* @__PURE__ */ React.createElement(EntityLinkCard, { key: `${item.linked_type}-${item.linked_id}-${item.id}`, item })))) : null, contributorLinks.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[30px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-sky-300/15 bg-sky-400/10 text-sky-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user-group text-sm" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Connected Creators"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-semibold text-white" }, "Creators and artworks that give the set its shape"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, contributorLinks.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, contributorLinks.map((item) => /* @__PURE__ */ React.createElement(EntityLinkCard, { key: `${item.linked_type}-${item.linked_id}-${item.id}`, item })))) : null, renderedFullModules.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-8 space-y-6" }, renderedFullModules) : null, renderedMainModules.length || renderedSidebarModules.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, renderedMainModules), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, renderedSidebarModules)) : null))); } -const __vite_glob_0_32 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_36 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionShow }, Symbol.toStringTag, { value: "Module" })); @@ -71518,7 +75465,7 @@ function buildProgramUrl(pattern, programKey) { return pattern.replace("__PROGRAM__", String(programKey)); } function CollectionStaffProgramming() { - const { props } = X(); + const { props } = X$1(); const initialCollectionOptions = Array.isArray(props.collectionOptions) ? props.collectionOptions : []; const initialAssignments = Array.isArray(props.assignments) ? props.assignments : []; const baseProgramKeys = Array.isArray(props.programKeyOptions) ? props.programKeyOptions : []; @@ -71793,7 +75740,7 @@ function CollectionStaffProgramming() { } const totalPrograms = Array.from(new Set(assignments.map((assignment) => assignment.program_key).filter(Boolean))).length; const eligibleAssignments = assignments.filter((assignment) => assignment.collection?.placement_eligibility === true).length; - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collection Programming — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Staff programming tools for collections." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collection Programming — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Staff programming tools for collections." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement( ShareToast, { key: toast.id, @@ -71809,7 +75756,7 @@ function CollectionStaffProgramming() { return /* @__PURE__ */ React.createElement("div", { key: `merge-pending-${item.id}`, className: `rounded-[24px] border border-white/10 bg-white/[0.04] p-4 transition ${cardBusy ? "ring-1 ring-sky-300/25" : ""}` }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (item.comparison?.match_reasons || []).map((reason) => /* @__PURE__ */ React.createElement("span", { key: reason, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-300" }, titleize(reason)))), cardBusy ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-notch fa-spin fa-fw text-[10px]" }), "Processing ", titleize(activeQueueAction)) : null), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Source"), item.source ? /* @__PURE__ */ React.createElement(CollectionCard, { collection: item.source, isOwner: true }) : null), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Candidate"), item.target ? /* @__PURE__ */ React.createElement(CollectionCard, { collection: item.target, isOwner: true }) : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-2 md:grid-cols-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Shared artworks: ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, item.comparison?.shared_artworks_count ?? 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Source count: ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, item.comparison?.source_artworks_count ?? 0)), /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2" }, "Target count: ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, item.comparison?.target_artworks_count ?? 0))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, item.source?.manage_url ? /* @__PURE__ */ React.createElement("a", { href: item.source.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-xs font-semibold text-rose-100 transition hover:bg-rose-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-code-compare fa-fw text-[10px]" }), "Review source") : null, item.target?.manage_url ? /* @__PURE__ */ React.createElement("a", { href: item.target.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square fa-fw text-[10px]" }), "Open target") : null, item.source?.id && historyPattern ? /* @__PURE__ */ React.createElement("a", { href: historyPattern.replace("__COLLECTION__", String(item.source.id)), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-timeline fa-fw text-[10px]" }), "History") : null, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleQueueAction("canonicalize", item), disabled: cardBusy, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-xs font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${activeQueueAction === "canonicalize" ? "fa-circle-notch fa-spin" : "fa-badge-check"} fa-fw text-[10px]` }), activeQueueAction === "canonicalize" ? "Canonicalizing..." : "Canonicalize"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleQueueAction("merge", item), disabled: cardBusy, className: "inline-flex items-center gap-2 rounded-full border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-xs font-semibold text-emerald-100 transition hover:bg-emerald-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${activeQueueAction === "merge" ? "fa-circle-notch fa-spin" : "fa-code-merge"} fa-fw text-[10px]` }), activeQueueAction === "merge" ? "Merging..." : "Merge now"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleQueueAction("reject", item), disabled: cardBusy, className: "inline-flex items-center gap-2 rounded-full border border-amber-300/20 bg-amber-400/10 px-4 py-2 text-xs font-semibold text-amber-100 transition hover:bg-amber-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${activeQueueAction === "reject" ? "fa-circle-notch fa-spin" : "fa-ban"} fa-fw text-[10px]` }), activeQueueAction === "reject" ? "Rejecting..." : "Reject"))); }) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-5 py-10 text-sm text-slate-300" }, "No pending merge candidates right now."))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Recent Decisions"), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-xl font-semibold text-white" }, "Canonical, reject, and merge history")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, mergeQueue?.recent?.length || 0)), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-4" }, (mergeQueue?.recent || []).length ? mergeQueue.recent.map((item) => /* @__PURE__ */ React.createElement("div", { key: `merge-recent-${item.id}`, className: "rounded-[24px] border border-white/10 bg-white/[0.04] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "inline-flex rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, titleize(item.action_type)), item.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, item.summary) : null, /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs text-slate-500" }, item.updated_at ? new Date(item.updated_at).toLocaleString() : "Unknown time", item.actor?.username ? ` • @${item.actor.username}` : ""))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 lg:grid-cols-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Source"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, item.source?.title || "Collection")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Target"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, item.target?.title || "Collection"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, item.source?.manage_url ? /* @__PURE__ */ React.createElement("a", { href: item.source.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen-to-square fa-fw text-[10px]" }), "Open source") : null, item.target?.manage_url ? /* @__PURE__ */ React.createElement("a", { href: item.target.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square fa-fw text-[10px]" }), "Open target") : null))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-5 py-10 text-sm text-slate-300" }, "No recent merge decisions yet."))))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleAssignmentSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Assignment"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Program key and scope")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field$1, { label: "Collection" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(assignmentForm.collection_id || ""), onChange: (val) => setAssignmentForm((current) => ({ ...current, collection_id: val })), options: collectionOptions.map((o) => ({ value: String(o.id), label: o.title })) })), /* @__PURE__ */ React.createElement(Field$1, { label: "Program Key", help: "Use stable internal names like discover-spring or homepage-hero." }, /* @__PURE__ */ React.createElement("input", { list: "program-key-options", value: assignmentForm.program_key, onChange: (event) => setAssignmentForm((current) => ({ ...current, program_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Placement Scope", help: "Optional placement scope such as homepage.hero or discover.rail." }, /* @__PURE__ */ React.createElement("input", { value: assignmentForm.placement_scope, onChange: (event) => setAssignmentForm((current) => ({ ...current, placement_scope: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Campaign Key" }, /* @__PURE__ */ React.createElement("input", { value: assignmentForm.campaign_key, onChange: (event) => setAssignmentForm((current) => ({ ...current, campaign_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Priority" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "-100", max: "100", value: assignmentForm.priority, onChange: (event) => setAssignmentForm((current) => ({ ...current, priority: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(Field$1, { label: "Starts At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: assignmentForm.starts_at, onChange: (nextValue) => setAssignmentForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field$1, { label: "Ends At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: assignmentForm.ends_at, onChange: (nextValue) => setAssignmentForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" }))), /* @__PURE__ */ React.createElement(Field$1, { label: "Notes", help: "Operational note for launch timing, overrides, or review context." }, /* @__PURE__ */ React.createElement("textarea", { value: assignmentForm.notes, onChange: (event) => setAssignmentForm((current) => ({ ...current, notes: event.target.value })), className: "mt-4 min-h-[120px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 1e3 })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "assignment", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "assignment" ? "fa-circle-notch fa-spin" : "fa-sliders"} fa-fw` }), assignmentForm.id ? "Update Assignment" : "Save Assignment"), assignmentForm.id ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetAssignmentForm, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Cancel Edit") : null), /* @__PURE__ */ React.createElement("datalist", { id: "program-key-options" }, programKeyOptions.map((option) => /* @__PURE__ */ React.createElement("option", { key: option, value: option })))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handlePreview, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Preview"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Inspect a live program pool")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_140px_auto]" }, /* @__PURE__ */ React.createElement(Field$1, { label: "Program Key" }, /* @__PURE__ */ React.createElement("input", { list: "program-key-options", value: previewForm.program_key, onChange: (event) => setPreviewForm((current) => ({ ...current, program_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Limit" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "1", max: "24", value: previewForm.limit, onChange: (event) => setPreviewForm((current) => ({ ...current, limit: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "preview", className: "inline-flex h-[50px] w-full items-center justify-center gap-2 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-5 text-sm font-semibold text-amber-100 transition hover:bg-amber-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "preview" ? "fa-circle-notch fa-spin" : "fa-binoculars"} fa-fw` }), "Preview"))), previewCollections.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 xl:grid-cols-2" }, previewCollections.map((collection) => /* @__PURE__ */ React.createElement("div", { key: collection.id, className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4" }, /* @__PURE__ */ React.createElement(CollectionCard, { collection, isOwner: true })))) : /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-5 py-8 text-sm text-slate-300" }, "Run a preview to inspect which collections currently qualify for a given program key.")), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-lime-200/80" }, "Diagnostics"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Eligibility, duplicate risk, and ranking refresh")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 xl:grid-cols-[320px_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Operations summary"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-1" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-400" }, "Stale health"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Number(observabilitySummary?.counts?.stale_health || 0))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-400" }, "Stale recommendations"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Number(observabilitySummary?.counts?.stale_recommendations || 0))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-400" }, "Placement blocked"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Number(observabilitySummary?.counts?.placement_blocked || 0))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-400" }, "Duplicate risk"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, Number(observabilitySummary?.counts?.duplicate_risk || 0)))), observabilitySummary?.generated_at ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-xs text-slate-400" }, "Generated ", new Date(observabilitySummary.generated_at).toLocaleString()) : null), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Watchlist"), Array.isArray(observabilitySummary?.watchlist) && observabilitySummary.watchlist.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 xl:grid-cols-2" }, observabilitySummary.watchlist.map((collection) => /* @__PURE__ */ React.createElement("div", { key: `watch-${collection.id}`, className: "rounded-[20px] border border-white/10 bg-white/[0.04] p-3" }, /* @__PURE__ */ React.createElement(CollectionCard, { collection, isOwner: true })))) : /* @__PURE__ */ React.createElement("p", { className: "mt-3" }, "No watchlist items are currently flagged."))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]" }, /* @__PURE__ */ React.createElement(Field$1, { label: "Target Collection", help: "Leave a selection in place to inspect one collection. Change it any time before running a diagnostic." }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(selectedCollectionId || ""), onChange: (val) => setSelectedCollectionId(val), options: collectionOptions.map((o) => ({ value: String(o.id), label: o.title })) })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => runDiagnostic("eligibility"), disabled: busy !== "", className: "inline-flex items-center gap-2 rounded-2xl border border-lime-300/20 bg-lime-400/10 px-4 py-3 text-sm font-semibold text-lime-100 transition hover:bg-lime-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "eligibility" ? "fa-circle-notch fa-spin" : "fa-shield-check"} fa-fw` }), "Eligibility"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => runDiagnostic("duplicates"), disabled: busy !== "", className: "inline-flex items-center gap-2 rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "duplicates" ? "fa-circle-notch fa-spin" : "fa-id-card"} fa-fw` }), "Duplicates"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => runDiagnostic("recommendations"), disabled: busy !== "", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "recommendations" ? "fa-circle-notch fa-spin" : "fa-arrows-rotate"} fa-fw` }), "Refresh"))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Eligibility"), diagnostics.eligibility ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, /* @__PURE__ */ React.createElement("p", null, diagnostics.eligibility.status === "queued" ? `${diagnostics.eligibility.count} collection(s) queued.` : `${diagnostics.eligibility.count} collection(s) evaluated.`), diagnostics.eligibility.message ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, diagnostics.eligibility.message) : null, (diagnostics.eligibility.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.collection_id, className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, item.health_state || "unknown", " · ", item.readiness_state || "unknown", " · ", item.placement_eligibility ? "eligible" : "blocked"))) : /* @__PURE__ */ React.createElement("p", { className: "mt-3" }, "Run an eligibility refresh to verify readiness and public placement safety.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Duplicate candidates"), diagnostics.duplicates ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, /* @__PURE__ */ React.createElement("p", null, diagnostics.duplicates.status === "queued" ? `${diagnostics.duplicates.count} collection(s) queued.` : `${diagnostics.duplicates.count} collection(s) with candidates.`), diagnostics.duplicates.message ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, diagnostics.duplicates.message) : null, (diagnostics.duplicates.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.collection_id, className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, item.candidates?.length ? item.candidates.map((candidate) => candidate.title).join(", ") : "No candidates"))) : /* @__PURE__ */ React.createElement("p", { className: "mt-3" }, "Run duplicate scan to surface overlap before programming a collection widely.")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Recommendation refresh"), diagnostics.recommendations ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, /* @__PURE__ */ React.createElement("p", null, diagnostics.recommendations.status === "queued" ? `${diagnostics.recommendations.count} collection(s) queued.` : `${diagnostics.recommendations.count} collection(s) refreshed.`), diagnostics.recommendations.message ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, diagnostics.recommendations.message) : null, (diagnostics.recommendations.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.collection_id, className: "rounded-2xl border border-white/10 bg-white/[0.04] px-3 py-2" }, titleize(item.recommendation_tier || "unknown"), " · ", titleize(item.ranking_bucket || "unknown"), " · ", titleize(item.search_boost_tier || "unknown")))) : /* @__PURE__ */ React.createElement("p", { className: "mt-3" }, "Run a recommendation refresh to update ranking and search tiers for this collection.")))), /* @__PURE__ */ React.createElement("form", { onSubmit: handleHooksSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-fuchsia-200/80" }, "Hooks"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Experiment and program governance"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-relaxed text-slate-300" }, "Control experiment keys, promotion tiers, and staff-only program governance hooks for the selected collection without leaving the programming studio.")), selectedCollection?.program_key && endpoints.publicProgramPattern ? /* @__PURE__ */ React.createElement("a", { href: buildProgramUrl(endpoints.publicProgramPattern, selectedCollection.program_key), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square fa-fw text-[11px]" }), "Open public program landing") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement(Field$1, { label: "Experiment Key", help: "Internal test or treatment key for cross-surface collection experiments." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.experiment_key, onChange: (event) => setHooksForm((current) => ({ ...current, experiment_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Treatment", help: "Variant or treatment label tied to the experiment key." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.experiment_treatment, onChange: (event) => setHooksForm((current) => ({ ...current, experiment_treatment: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Placement Variant", help: "Surface-specific placement variant such as homepage_a or search_dense." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.placement_variant, onChange: (event) => setHooksForm((current) => ({ ...current, placement_variant: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Ranking Variant", help: "Override or annotate ranking mode experiments without changing the live pool logic." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.ranking_mode_variant, onChange: (event) => setHooksForm((current) => ({ ...current, ranking_mode_variant: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Pool Version", help: "Snapshot or rollout version for the collection pool definition." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.collection_pool_version, onChange: (event) => setHooksForm((current) => ({ ...current, collection_pool_version: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Test Label", help: "Human-readable campaign or experiment label for operations and diagnostics." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.test_label, onChange: (event) => setHooksForm((current) => ({ ...current, test_label: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 120 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Promotion Tier", help: "Optional internal tier for elevated or restrained programming treatment." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.promotion_tier, onChange: (event) => setHooksForm((current) => ({ ...current, promotion_tier: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 40 })), viewer.isAdmin ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Field$1, { label: "Partner Key", help: "Admin-only internal key for trusted partner or program ownership." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.partner_key, onChange: (event) => setHooksForm((current) => ({ ...current, partner_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Trust Tier", help: "Admin-only trust marker used for internal partner/program review logic." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.trust_tier, onChange: (event) => setHooksForm((current) => ({ ...current, trust_tier: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 40 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Sponsorship State", help: "Admin-only state for sponsored, pending, or cleared program treatment." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.sponsorship_state, onChange: (event) => setHooksForm((current) => ({ ...current, sponsorship_state: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 40 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Ownership Domain", help: "Admin-only internal ownership domain such as editorial, partner, creator_program, or events." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.ownership_domain, onChange: (event) => setHooksForm((current) => ({ ...current, ownership_domain: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Commercial Review", help: "Admin-only commercial review status for future partner and sponsor programs." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.commercial_review_state, onChange: (event) => setHooksForm((current) => ({ ...current, commercial_review_state: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 40 })), /* @__PURE__ */ React.createElement(Field$1, { label: "Legal Review", help: "Admin-only legal review status when collections need compliance approval before wider promotion." }, /* @__PURE__ */ React.createElement("input", { value: hooksForm.legal_review_state, onChange: (event) => setHooksForm((current) => ({ ...current, legal_review_state: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 40 }))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/12 bg-white/[0.03] px-4 py-4 text-sm text-slate-300 md:col-span-2 xl:col-span-3" }, "Partner, sponsorship, ownership, and review metadata remain admin-only. Moderators can still manage experiment and promotion hooks here.")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center gap-3 rounded-[20px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: hooksForm.placement_eligibility, onChange: (event) => setHooksForm((current) => ({ ...current, placement_eligibility: event.target.checked })), label: "Placement eligible override" })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Experiment"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.experiment_key || selectedCollection?.experiment_key || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Treatment"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.experiment_treatment || selectedCollection?.experiment_treatment || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Placement Variant"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.placement_variant || selectedCollection?.placement_variant || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Workflow"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, titleize(hooksDiagnostics?.workflow_state || selectedCollection?.workflow_state || "unknown"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Health"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, titleize(hooksDiagnostics?.health_state || selectedCollection?.health_state || "unknown"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Recommendation Tier"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, titleize(hooksDiagnostics?.recommendation_tier || selectedCollection?.recommendation_tier || "unknown"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Ranking Bucket"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, titleize(hooksDiagnostics?.ranking_bucket || selectedCollection?.ranking_bucket || "unknown"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Ranking Variant"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.ranking_mode_variant || selectedCollection?.ranking_mode_variant || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Pool Version"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.collection_pool_version || selectedCollection?.collection_pool_version || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Test Label"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.test_label || selectedCollection?.test_label || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Promotion Tier"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.promotion_tier || selectedCollection?.promotion_tier || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Partner Key"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.partner_key || selectedCollection?.partner_key || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Trust Tier"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.trust_tier || selectedCollection?.trust_tier || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Sponsorship State"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.sponsorship_state || selectedCollection?.sponsorship_state || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Ownership Domain"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.ownership_domain || selectedCollection?.ownership_domain || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Commercial Review"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.commercial_review_state || selectedCollection?.commercial_review_state || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Legal Review"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.legal_review_state || selectedCollection?.legal_review_state || "Not set")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Last Health Check"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.last_health_check_at ? new Date(hooksDiagnostics.last_health_check_at).toLocaleString() : "Not yet")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-slate-950/40 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, "Last Recommendation Refresh"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, hooksDiagnostics?.last_recommendation_refresh_at ? new Date(hooksDiagnostics.last_recommendation_refresh_at).toLocaleString() : "Not yet"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "hooks" || !selectedCollectionId, className: "inline-flex items-center gap-2 rounded-2xl border border-fuchsia-300/20 bg-fuchsia-400/10 px-5 py-3 text-sm font-semibold text-fuchsia-100 transition hover:bg-fuchsia-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "hooks" ? "fa-circle-notch fa-spin" : "fa-flask-vial"} fa-fw` }), "Save Hooks"), selectedCollection?.manage_url ? /* @__PURE__ */ React.createElement("a", { href: selectedCollection.manage_url, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square fa-fw" }), "Open collection") : null)))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Assignments"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Current programming inventory")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, assignments.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-5" }, assignments.length ? assignments.map((assignment) => /* @__PURE__ */ React.createElement("div", { key: assignment.id, className: "rounded-[28px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, assignment.program_key), assignment.placement_scope ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, assignment.placement_scope) : null, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "priority ", assignment.priority)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => hydrateAssignment(assignment), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen fa-fw text-[10px]" }), "Edit"), endpoints.managePattern ? /* @__PURE__ */ React.createElement("a", { href: endpoints.managePattern.replace("__COLLECTION__", String(assignment.collection?.id || "")), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square fa-fw text-[10px]" }), "Manage") : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-5 xl:grid-cols-[minmax(0,1fr)_280px]" }, /* @__PURE__ */ React.createElement("div", null, assignment.collection ? /* @__PURE__ */ React.createElement(CollectionCard, { collection: assignment.collection, isOwner: true }) : null), /* @__PURE__ */ React.createElement("div", { className: "space-y-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Campaign: ", assignment.campaign_key || "None"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Starts: ", assignment.starts_at ? new Date(assignment.starts_at).toLocaleString() : "Immediate"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Ends: ", assignment.ends_at ? new Date(assignment.ends_at).toLocaleString() : "Open-ended"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Placement: ", assignment.collection?.placement_eligibility ? "Eligible" : "Blocked"), assignment.notes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, assignment.notes) : null)))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-12 text-sm text-slate-300" }, "No programming assignments yet. Create the first one above.")))))); } -const __vite_glob_0_33 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_37 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionStaffProgramming }, Symbol.toStringTag, { value: "Module" })); @@ -71854,7 +75801,7 @@ function Field({ label, help, children }) { return /* @__PURE__ */ React.createElement("label", { className: "block space-y-2" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, label), children, help ? /* @__PURE__ */ React.createElement("span", { className: "block text-xs leading-relaxed text-slate-400" }, help) : null); } function CollectionStaffSurfaces() { - const { props } = X(); + const { props } = X$1(); const collectionOptions = Array.isArray(props.collectionOptions) ? props.collectionOptions : []; const [definitions, setDefinitions] = React.useState(Array.isArray(props.definitions) ? props.definitions : []); const [placements, setPlacements] = React.useState(Array.isArray(props.placements) ? props.placements : []); @@ -72130,12 +76077,12 @@ function CollectionStaffSurfaces() { setBusy(""); } } - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collection Surfaces — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Staff tools for collection surfaces." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[34rem] opacity-95", style: { background: "radial-gradient(circle at 15% 14%, rgba(245,158,11,0.16), transparent 26%), radial-gradient(circle at 82% 18%, rgba(56,189,248,0.16), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Staff Surfaces"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Collections placement studio"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "Define reusable discovery surfaces, then place eligible public collections into manual or campaign-specific slots with clear timing and notes."), notice ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm text-sky-100" }, notice) : null), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleDefinitionSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Surface Definition"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Rules and ranking")), definitionForm.id ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, "Editing ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, definitionForm.surface_key)) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Surface Key", help: definitionForm.id ? "Surface keys stay stable during edits so existing placements remain attached." : null }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.surface_key, onChange: (event) => setDefinitionForm((current) => ({ ...current, surface_key: event.target.value })), disabled: Boolean(definitionForm.id), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none disabled:cursor-not-allowed disabled:opacity-60", maxLength: 120 })), /* @__PURE__ */ React.createElement(Field, { label: "Title" }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.title, onChange: (event) => setDefinitionForm((current) => ({ ...current, title: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 160 })), /* @__PURE__ */ React.createElement(Field, { label: "Mode" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: definitionForm.mode, onChange: (val) => setDefinitionForm((current) => ({ ...current, mode: val })), searchable: false, options: [{ value: "manual", label: "Manual" }, { value: "automatic", label: "Automatic" }, { value: "hybrid", label: "Hybrid" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Ranking" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: definitionForm.ranking_mode, onChange: (val) => setDefinitionForm((current) => ({ ...current, ranking_mode: val })), searchable: false, options: [{ value: "ranking_score", label: "Ranking score" }, { value: "recent_activity", label: "Recent activity" }, { value: "quality_score", label: "Quality score" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Max Items" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "1", max: "24", value: definitionForm.max_items, onChange: (event) => setDefinitionForm((current) => ({ ...current, max_items: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(Field, { label: "Starts At", help: "Optional activation window for the full surface definition." }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: definitionForm.starts_at, onChange: (nextValue) => setDefinitionForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Ends At", help: "Leave blank when the surface should stay live until staff changes it." }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: definitionForm.ends_at, onChange: (nextValue) => setDefinitionForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Fallback Surface Key", help: "Optional fallback when this definition is inactive, scheduled out, or resolves no items." }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.fallback_surface_key, onChange: (event) => setDefinitionForm((current) => ({ ...current, fallback_surface_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 120 })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: definitionForm.is_active, onChange: (event) => setDefinitionForm((current) => ({ ...current, is_active: event.target.checked })), label: "Active" }))), /* @__PURE__ */ React.createElement(Field, { label: "Description", help: "Operational note for staff browsing this surface later." }, /* @__PURE__ */ React.createElement("textarea", { value: definitionForm.description, onChange: (event) => setDefinitionForm((current) => ({ ...current, description: event.target.value })), className: "mt-4 min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 400 })), /* @__PURE__ */ React.createElement(Field, { label: "Rules JSON", help: "Supported filters include campaign, event, season, type, presentation_style, theme_token, collaboration_mode, owner_username or owner_usernames, commercial_eligible_only, analytics_enabled_only, min_quality_score, min_ranking_score, include_collection_ids, exclude_collection_ids, and featured_only." }, /* @__PURE__ */ React.createElement("textarea", { value: definitionForm.rules_json, onChange: (event) => setDefinitionForm((current) => ({ ...current, rules_json: event.target.value })), className: "mt-4 min-h-[160px] w-full rounded-2xl border border-white/10 bg-slate-950/50 px-4 py-3 font-mono text-sm text-white outline-none", spellCheck: false })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "definition", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "definition" ? "fa-circle-notch fa-spin" : "fa-layer-group"} fa-fw` }), definitionForm.id ? "Update Definition" : "Save Definition"), definitionForm.id ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetDefinitionForm, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Cancel Edit") : null)), /* @__PURE__ */ React.createElement("form", { onSubmit: handlePlacementSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Surface Placement"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Manual and campaign slots")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Surface" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: placementForm.surface_key, onChange: (val) => setPlacementForm((current) => ({ ...current, surface_key: val })), options: surfaceKeyOptions.map((o) => ({ value: o, label: o })) })), /* @__PURE__ */ React.createElement(Field, { label: "Collection" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(placementForm.collection_id || ""), onChange: (val) => setPlacementForm((current) => ({ ...current, collection_id: val })), options: collectionOptions.map((o) => ({ value: String(o.id), label: o.title })) })), /* @__PURE__ */ React.createElement(Field, { label: "Placement Type" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: placementForm.placement_type, onChange: (val) => setPlacementForm((current) => ({ ...current, placement_type: val })), searchable: false, options: [{ value: "manual", label: "Manual" }, { value: "campaign", label: "Campaign" }, { value: "scheduled_override", label: "Scheduled override" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Priority" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "-100", max: "100", value: placementForm.priority, onChange: (event) => setPlacementForm((current) => ({ ...current, priority: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(Field, { label: "Starts At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: placementForm.starts_at, onChange: (nextValue) => setPlacementForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Ends At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: placementForm.ends_at, onChange: (nextValue) => setPlacementForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Campaign Key", help: "Optional campaign label for reporting and grouped overrides." }, /* @__PURE__ */ React.createElement("input", { value: placementForm.campaign_key, onChange: (event) => setPlacementForm((current) => ({ ...current, campaign_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: placementForm.is_active, onChange: (event) => setPlacementForm((current) => ({ ...current, is_active: event.target.checked })), label: "Active placement" }))), /* @__PURE__ */ React.createElement(Field, { label: "Notes", help: "Internal note for why this collection owns the slot." }, /* @__PURE__ */ React.createElement("textarea", { value: placementForm.notes, onChange: (event) => setPlacementForm((current) => ({ ...current, notes: event.target.value })), className: "mt-4 min-h-[110px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 1e3 })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "placement", className: "inline-flex items-center gap-2 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:bg-amber-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "placement" ? "fa-circle-notch fa-spin" : "fa-thumbtack"} fa-fw` }), placementForm.id ? "Update Placement" : "Save Placement"), placementForm.id ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetPlacementForm, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Cancel Edit") : null))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-lime-200/80" }, "Batch Editorial Tools"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Campaign planning in one pass")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, batchForm.collection_ids.length, " selected")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, "Choose collections"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, "The selector uses current public discovery candidates so staff can quickly prepare a seasonal or editorial run."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, collectionOptions.map((option) => { + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Se$1, null, /* @__PURE__ */ React.createElement("title", null, seo.title || "Collection Surfaces — Skinbase"), /* @__PURE__ */ React.createElement("meta", { name: "description", content: seo.description || "Staff tools for collection surfaces." }), seo.canonical ? /* @__PURE__ */ React.createElement("link", { rel: "canonical", href: seo.canonical }) : null, /* @__PURE__ */ React.createElement("meta", { name: "robots", content: seo.robots || "noindex,follow" })), /* @__PURE__ */ React.createElement("div", { className: "relative min-h-screen overflow-hidden pb-16" }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 -z-10 h-[34rem] opacity-95", style: { background: "radial-gradient(circle at 15% 14%, rgba(245,158,11,0.16), transparent 26%), radial-gradient(circle at 82% 18%, rgba(56,189,248,0.16), transparent 24%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)" } }), /* @__PURE__ */ React.createElement("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 -z-10 opacity-[0.05]", style: { backgroundImage: "url(/gfx/noise.png)", backgroundSize: "180px" } }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pt-8 md:px-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Staff Surfaces"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl" }, "Collections placement studio"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]" }, "Define reusable discovery surfaces, then place eligible public collections into manual or campaign-specific slots with clear timing and notes."), notice ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm text-sky-100" }, notice) : null), /* @__PURE__ */ React.createElement("section", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]" }, /* @__PURE__ */ React.createElement("form", { onSubmit: handleDefinitionSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Surface Definition"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Rules and ranking")), definitionForm.id ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, "Editing ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, definitionForm.surface_key)) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Surface Key", help: definitionForm.id ? "Surface keys stay stable during edits so existing placements remain attached." : null }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.surface_key, onChange: (event) => setDefinitionForm((current) => ({ ...current, surface_key: event.target.value })), disabled: Boolean(definitionForm.id), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none disabled:cursor-not-allowed disabled:opacity-60", maxLength: 120 })), /* @__PURE__ */ React.createElement(Field, { label: "Title" }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.title, onChange: (event) => setDefinitionForm((current) => ({ ...current, title: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 160 })), /* @__PURE__ */ React.createElement(Field, { label: "Mode" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: definitionForm.mode, onChange: (val) => setDefinitionForm((current) => ({ ...current, mode: val })), searchable: false, options: [{ value: "manual", label: "Manual" }, { value: "automatic", label: "Automatic" }, { value: "hybrid", label: "Hybrid" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Ranking" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: definitionForm.ranking_mode, onChange: (val) => setDefinitionForm((current) => ({ ...current, ranking_mode: val })), searchable: false, options: [{ value: "ranking_score", label: "Ranking score" }, { value: "recent_activity", label: "Recent activity" }, { value: "quality_score", label: "Quality score" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Max Items" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "1", max: "24", value: definitionForm.max_items, onChange: (event) => setDefinitionForm((current) => ({ ...current, max_items: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(Field, { label: "Starts At", help: "Optional activation window for the full surface definition." }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: definitionForm.starts_at, onChange: (nextValue) => setDefinitionForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Ends At", help: "Leave blank when the surface should stay live until staff changes it." }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: definitionForm.ends_at, onChange: (nextValue) => setDefinitionForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Fallback Surface Key", help: "Optional fallback when this definition is inactive, scheduled out, or resolves no items." }, /* @__PURE__ */ React.createElement("input", { value: definitionForm.fallback_surface_key, onChange: (event) => setDefinitionForm((current) => ({ ...current, fallback_surface_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 120 })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: definitionForm.is_active, onChange: (event) => setDefinitionForm((current) => ({ ...current, is_active: event.target.checked })), label: "Active" }))), /* @__PURE__ */ React.createElement(Field, { label: "Description", help: "Operational note for staff browsing this surface later." }, /* @__PURE__ */ React.createElement("textarea", { value: definitionForm.description, onChange: (event) => setDefinitionForm((current) => ({ ...current, description: event.target.value })), className: "mt-4 min-h-[96px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 400 })), /* @__PURE__ */ React.createElement(Field, { label: "Rules JSON", help: "Supported filters include campaign, event, season, type, presentation_style, theme_token, collaboration_mode, owner_username or owner_usernames, commercial_eligible_only, analytics_enabled_only, min_quality_score, min_ranking_score, include_collection_ids, exclude_collection_ids, and featured_only." }, /* @__PURE__ */ React.createElement("textarea", { value: definitionForm.rules_json, onChange: (event) => setDefinitionForm((current) => ({ ...current, rules_json: event.target.value })), className: "mt-4 min-h-[160px] w-full rounded-2xl border border-white/10 bg-slate-950/50 px-4 py-3 font-mono text-sm text-white outline-none", spellCheck: false })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "definition", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "definition" ? "fa-circle-notch fa-spin" : "fa-layer-group"} fa-fw` }), definitionForm.id ? "Update Definition" : "Save Definition"), definitionForm.id ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetDefinitionForm, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Cancel Edit") : null)), /* @__PURE__ */ React.createElement("form", { onSubmit: handlePlacementSubmit, className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Surface Placement"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Manual and campaign slots")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Surface" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: placementForm.surface_key, onChange: (val) => setPlacementForm((current) => ({ ...current, surface_key: val })), options: surfaceKeyOptions.map((o) => ({ value: o, label: o })) })), /* @__PURE__ */ React.createElement(Field, { label: "Collection" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(placementForm.collection_id || ""), onChange: (val) => setPlacementForm((current) => ({ ...current, collection_id: val })), options: collectionOptions.map((o) => ({ value: String(o.id), label: o.title })) })), /* @__PURE__ */ React.createElement(Field, { label: "Placement Type" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: placementForm.placement_type, onChange: (val) => setPlacementForm((current) => ({ ...current, placement_type: val })), searchable: false, options: [{ value: "manual", label: "Manual" }, { value: "campaign", label: "Campaign" }, { value: "scheduled_override", label: "Scheduled override" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Priority" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "-100", max: "100", value: placementForm.priority, onChange: (event) => setPlacementForm((current) => ({ ...current, priority: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement(Field, { label: "Starts At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: placementForm.starts_at, onChange: (nextValue) => setPlacementForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Ends At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: placementForm.ends_at, onChange: (nextValue) => setPlacementForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Campaign Key", help: "Optional campaign label for reporting and grouped overrides." }, /* @__PURE__ */ React.createElement("input", { value: placementForm.campaign_key, onChange: (event) => setPlacementForm((current) => ({ ...current, campaign_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: placementForm.is_active, onChange: (event) => setPlacementForm((current) => ({ ...current, is_active: event.target.checked })), label: "Active placement" }))), /* @__PURE__ */ React.createElement(Field, { label: "Notes", help: "Internal note for why this collection owns the slot." }, /* @__PURE__ */ React.createElement("textarea", { value: placementForm.notes, onChange: (event) => setPlacementForm((current) => ({ ...current, notes: event.target.value })), className: "mt-4 min-h-[110px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 1e3 })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy === "placement", className: "inline-flex items-center gap-2 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:bg-amber-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "placement" ? "fa-circle-notch fa-spin" : "fa-thumbtack"} fa-fw` }), placementForm.id ? "Update Placement" : "Save Placement"), placementForm.id ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetPlacementForm, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rotate-left fa-fw" }), "Cancel Edit") : null))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-lime-200/80" }, "Batch Editorial Tools"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Campaign planning in one pass")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, batchForm.collection_ids.length, " selected")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, "Choose collections"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, "The selector uses current public discovery candidates so staff can quickly prepare a seasonal or editorial run."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, collectionOptions.map((option) => { const checked = batchForm.collection_ids.includes(option.id); return /* @__PURE__ */ React.createElement("label", { key: option.id, className: `flex cursor-pointer items-start gap-3 rounded-[22px] border px-4 py-3 transition ${checked ? "border-lime-300/30 bg-lime-400/10" : "border-white/10 bg-white/[0.04] hover:bg-white/[0.07]"}` }, /* @__PURE__ */ React.createElement(Checkbox, { checked, onChange: () => toggleBatchCollection(option.id) }), /* @__PURE__ */ React.createElement("span", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block truncate text-sm font-semibold text-white" }, option.title), /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-xs text-slate-400" }, option.type || "collection", " · ", option.visibility || "public"))); }))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, "Campaign metadata"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Campaign Key" }, /* @__PURE__ */ React.createElement("input", { value: batchForm.campaign_key, onChange: (event) => setBatchForm((current) => ({ ...current, campaign_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 })), /* @__PURE__ */ React.createElement(Field, { label: "Campaign Label" }, /* @__PURE__ */ React.createElement("input", { value: batchForm.campaign_label, onChange: (event) => setBatchForm((current) => ({ ...current, campaign_label: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 120 })), /* @__PURE__ */ React.createElement(Field, { label: "Event Label" }, /* @__PURE__ */ React.createElement("input", { value: batchForm.event_label, onChange: (event) => setBatchForm((current) => ({ ...current, event_label: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 120 })), /* @__PURE__ */ React.createElement(Field, { label: "Season Key" }, /* @__PURE__ */ React.createElement("input", { value: batchForm.season_key, onChange: (event) => setBatchForm((current) => ({ ...current, season_key: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 80 }))), /* @__PURE__ */ React.createElement(Field, { label: "Editorial Notes", help: "Shared context recorded on each selected collection." }, /* @__PURE__ */ React.createElement("textarea", { value: batchForm.editorial_notes, onChange: (event) => setBatchForm((current) => ({ ...current, editorial_notes: event.target.value })), className: "mt-4 min-h-[120px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 4e3 })))), /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, "Optional placement plan"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, "If you set a surface, the preview shows which collections can safely be placed and which ones will be skipped."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(Field, { label: "Surface" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: batchForm.surface_key, onChange: (val) => setBatchForm((current) => ({ ...current, surface_key: val })), placeholder: "No placement", options: surfaceKeyOptions.map((o) => ({ value: o, label: o })) })), /* @__PURE__ */ React.createElement(Field, { label: "Placement Type" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: batchForm.placement_type, onChange: (val) => setBatchForm((current) => ({ ...current, placement_type: val })), searchable: false, options: [{ value: "campaign", label: "Campaign" }, { value: "manual", label: "Manual" }, { value: "scheduled_override", label: "Scheduled override" }] })), /* @__PURE__ */ React.createElement(Field, { label: "Priority" }, /* @__PURE__ */ React.createElement("input", { type: "number", min: "-100", max: "100", value: batchForm.priority, onChange: (event) => setBatchForm((current) => ({ ...current, priority: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: batchForm.is_active, onChange: (event) => setBatchForm((current) => ({ ...current, is_active: event.target.checked })), label: "Active placement" })), /* @__PURE__ */ React.createElement(Field, { label: "Starts At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: batchForm.starts_at, onChange: (nextValue) => setBatchForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Start time", clearable: true, className: "bg-white/[0.04]" })), /* @__PURE__ */ React.createElement(Field, { label: "Ends At" }, /* @__PURE__ */ React.createElement(DateTimePicker, { value: batchForm.ends_at, onChange: (nextValue) => setBatchForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "End time", clearable: true, className: "bg-white/[0.04]" }))), /* @__PURE__ */ React.createElement(Field, { label: "Placement Notes" }, /* @__PURE__ */ React.createElement("textarea", { value: batchForm.notes, onChange: (event) => setBatchForm((current) => ({ ...current, notes: event.target.value })), className: "mt-4 min-h-[110px] w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-white outline-none", maxLength: 1e3 })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleBatchEditorial("preview"), disabled: busy === "batch-preview", className: "inline-flex items-center gap-2 rounded-2xl border border-lime-300/20 bg-lime-400/10 px-5 py-3 text-sm font-semibold text-lime-100 transition hover:bg-lime-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "batch-preview" ? "fa-circle-notch fa-spin" : "fa-flask"} fa-fw` }), "Preview Batch"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleBatchEditorial("apply"), disabled: busy === "batch-apply", className: "inline-flex items-center gap-2 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:bg-amber-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === "batch-apply" ? "fa-circle-notch fa-spin" : "fa-wand-magic-sparkles"} fa-fw` }), "Apply Batch"))), batchResult ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[26px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, "Preview results"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-300" }, batchResult.collections_count, " collections reviewed, ", batchResult.placement_eligible_count, " placement-ready."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (batchResult.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.collection?.id, className: "rounded-[22px] border border-white/10 bg-white/[0.04] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold text-white" }, item.collection?.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs text-slate-400" }, item.collection?.visibility, " · ", item.collection?.lifecycle_state, " · ", item.collection?.moderation_status)), item.placement ? /* @__PURE__ */ React.createElement("span", { className: `rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] ${item.placement.eligible ? "border-lime-300/20 bg-lime-400/10 text-lime-100" : "border-rose-300/20 bg-rose-400/10 text-rose-100"}` }, item.placement.eligible ? `ready for ${item.placement.surface_key}` : "placement skipped") : /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "metadata only")), item.eligibility?.reasons?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs text-amber-100/80" }, "Campaign readiness: ", item.eligibility.reasons.join(" ")) : null, item.placement?.reasons?.length ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs text-rose-100/80" }, "Placement: ", item.placement.reasons.join(" ")) : null)))) : null))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Definitions"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Registered surfaces")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, definitions.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, definitions.map((definition2) => /* @__PURE__ */ React.createElement("div", { key: definition2.id, className: "rounded-[24px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, definition2.surface_key), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, definition2.mode)), /* @__PURE__ */ React.createElement("h3", { className: "mt-4 text-lg font-semibold text-white" }, definition2.title), definition2.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, definition2.description) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, definition2.ranking_mode), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "max ", definition2.max_items), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, definition2.is_active ? "active" : "inactive"), definition2.starts_at ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "starts ", new Date(definition2.starts_at).toLocaleString()) : null, definition2.ends_at ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "ends ", new Date(definition2.ends_at).toLocaleString()) : null, definition2.fallback_surface_key ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, "fallback ", definition2.fallback_surface_key) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => hydrateDefinition(definition2), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen fa-fw text-[10px]" }), "Edit Definition"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleDeleteDefinition(definition2), disabled: busy === `delete-definition-${definition2.id}`, className: "inline-flex items-center gap-2 rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-xs font-semibold text-rose-100 transition hover:bg-rose-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `delete-definition-${definition2.id}` ? "fa-circle-notch fa-spin" : "fa-trash"} fa-fw text-[10px]` }), "Delete"))))))), conflicts.length ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-rose-300/20 bg-rose-500/10 p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-rose-100/80" }, "Conflicts"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Schedule overlaps need review")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-1 text-xs font-semibold text-rose-100" }, conflicts.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, conflicts.map((conflict, index2) => /* @__PURE__ */ React.createElement("div", { key: `${conflict.surface_key}-${index2}`, className: "rounded-[24px] border border-rose-300/20 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-rose-100" }, conflict.surface_key)), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm text-rose-50" }, conflict.summary), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs text-rose-100/70" }, "Window: ", conflict.window?.starts_at ? new Date(conflict.window.starts_at).toLocaleString() : "Immediate", " to ", conflict.window?.ends_at ? new Date(conflict.window.ends_at).toLocaleString() : "Open-ended"))))) : null, /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Placements"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Active and scheduled slots")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, placements.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-5" }, placements.map((placement) => /* @__PURE__ */ React.createElement("div", { key: placement.id, className: "rounded-[28px] border border-white/10 bg-slate-950/40 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, placement.surface_key), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, placement.placement_type), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-300" }, "priority ", placement.priority), conflictPlacementIds.has(placement.id) || placement.has_conflict ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-rose-100" }, "conflict") : null), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => hydratePlacement(placement), className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/[0.07]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-pen fa-fw text-[10px]" }), "Edit"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleDeletePlacement(placement), disabled: busy === `delete-placement-${placement.id}`, className: "inline-flex items-center gap-2 rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-xs font-semibold text-rose-100 transition hover:bg-rose-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `delete-placement-${placement.id}` ? "fa-circle-notch fa-spin" : "fa-trash"} fa-fw text-[10px]` }), "Delete"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-5 xl:grid-cols-[minmax(0,1fr)_280px]" }, /* @__PURE__ */ React.createElement("div", null, placement.collection ? /* @__PURE__ */ React.createElement(CollectionCard, { collection: placement.collection, isOwner: true }) : null), /* @__PURE__ */ React.createElement("div", { className: "space-y-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Starts: ", placement.starts_at ? new Date(placement.starts_at).toLocaleString() : "Immediate"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Ends: ", placement.ends_at ? new Date(placement.ends_at).toLocaleString() : "Open-ended"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Campaign: ", placement.campaign_key || "None"), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3" }, "Status: ", placement.is_active ? "Active" : "Inactive"), placement.notes ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-white/[0.04] px-4 py-3 text-slate-300" }, placement.notes) : null))))))))); } -const __vite_glob_0_34 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_38 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CollectionStaffSurfaces }, Symbol.toStringTag, { value: "Module" })); @@ -72446,7 +76393,7 @@ function renderOverrideHistoryItems(items, prefix) { return (items || []).slice(0, 3).map((entry, index2) => /* @__PURE__ */ React.createElement("div", { key: `${prefix}-${index2}-${entry.updated_at || entry.source || entry.moderation_status || "override"}`, className: "rounded-2xl border border-white/10 bg-black/10 px-3 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-sky-100/75" }, /* @__PURE__ */ React.createElement("span", null, entry.moderation_status || "unknown status"), entry.disposition_label ? /* @__PURE__ */ React.createElement("span", null, entry.disposition_label) : null, entry.actor_username ? /* @__PURE__ */ React.createElement("span", null, "@", entry.actor_username) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-1 flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.14em] text-sky-100/55" }, entry.source ? /* @__PURE__ */ React.createElement("span", null, String(entry.source).replaceAll("_", " ")) : null, entry.updated_at ? /* @__PURE__ */ React.createElement("span", null, new Date(entry.updated_at).toLocaleString()) : null), entry.note ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm leading-6 text-sky-50" }, entry.note) : null)); } function NovaCardsAdminIndex() { - const { props } = X(); + const { props } = X$1(); const [cards, setCards] = React.useState(props.cards?.data || []); const [featuredCreators, setFeaturedCreators] = React.useState(props.featuredCreators || []); const [categories, setCategories] = React.useState(props.categories || []); @@ -72622,7 +76569,7 @@ function NovaCardsAdminIndex() { setReportBusy((current) => ({ ...current, [reportId]: false })); } } - return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Nova Cards Moderation" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Moderation surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Nova Cards control panel"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Review pending cards, feature standout work, and keep the starter category taxonomy healthy as Nova Cards launches.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: endpoints.templates || "/cp/cards/templates", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-swatchbook" }), "Manage templates"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.assetPacks || "/cp/cards/asset-packs", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-shapes" }), "Asset packs"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.challenges || "/cp/cards/challenges", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-trophy" }), "Challenges")))), /* @__PURE__ */ React.createElement("section", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, [ + return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Nova Cards Moderation" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Moderation surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Nova Cards control panel"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Review pending cards, feature standout work, and keep the starter category taxonomy healthy as Nova Cards launches.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement(xe, { href: endpoints.templates || "/cp/cards/templates", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-swatchbook" }), "Manage templates"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.assetPacks || "/cp/cards/asset-packs", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-shapes" }), "Asset packs"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.challenges || "/cp/cards/challenges", className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-trophy" }), "Challenges")))), /* @__PURE__ */ React.createElement("section", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, [ ["Pending", stats.pending || 0, "fa-clock"], ["Flagged", stats.flagged || 0, "fa-flag"], ["Featured", stats.featured || 0, "fa-star"], @@ -72704,7 +76651,7 @@ function NovaCardsAdminIndex() { } )), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Featured"), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(card.featured), onChange: (event) => updateCard(card.id, { featured: event.target.checked }) })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Allow remix"), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(card.allow_remix), onChange: (event) => updateCard(card.id, { allow_remix: event.target.checked }) }))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-4 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, card.likes_count || 0, " likes"), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, card.saves_count || 0, " saves"), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, card.remixes_count || 0, " remixes"), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, card.challenge_entries_count || 0, " challenge entries")), card.moderation_reason_labels?.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-2xl border border-amber-300/15 bg-amber-400/10 px-4 py-3 text-sm text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Heuristic moderation flags"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap gap-2" }, card.moderation_reason_labels.map((label) => /* @__PURE__ */ React.createElement("span", { key: `${card.id}-${label}`, className: "rounded-full border border-amber-200/20 bg-amber-50/10 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-amber-50" }, label))), card.moderation_source ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-[11px] uppercase tracking-[0.14em] text-amber-100/70" }, "Source ", String(card.moderation_source).replaceAll("_", " ")) : null) : null, card.moderation_override ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-2xl border border-sky-300/15 bg-sky-400/10 px-4 py-3 text-sm text-sky-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-100/80" }, "Latest staff override"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap gap-2 text-xs uppercase tracking-[0.14em] text-sky-100/80" }, /* @__PURE__ */ React.createElement("span", null, "Status ", card.moderation_override.moderation_status), card.moderation_override.disposition_label ? /* @__PURE__ */ React.createElement("span", null, card.moderation_override.disposition_label) : null, card.moderation_override.actor_username ? /* @__PURE__ */ React.createElement("span", null, "@", card.moderation_override.actor_username) : null, card.moderation_override.source ? /* @__PURE__ */ React.createElement("span", null, String(card.moderation_override.source).replaceAll("_", " ")) : null), card.moderation_override.note ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm leading-6 text-sky-50" }, card.moderation_override.note) : null) : null, card.moderation_override_history?.length > 1 ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 rounded-2xl border border-sky-300/10 bg-sky-400/[0.08] px-4 py-3 text-sm text-sky-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-100/75" }, "Recent override history"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, renderOverrideHistoryItems(card.moderation_override_history, `card-${card.id}`))) : null))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Creator curation"), !featuredCreators.length ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-4 text-sm text-slate-400" }, "No public Nova creators are available for curation yet.") : null, /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, featuredCreators.map((creator) => /* @__PURE__ */ React.createElement("div", { key: creator.id, className: "rounded-2xl border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, creator.display_name), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, "@", creator.username)), creator.public_url ? /* @__PURE__ */ React.createElement("a", { href: creator.public_url, className: "text-xs font-semibold uppercase tracking-[0.16em] text-sky-300 transition hover:text-sky-200" }, "Open profile") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-3 grid grid-cols-3 gap-2 text-xs text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-[#08111f]/70 px-3 py-3" }, creator.public_cards_count || 0, " public cards"), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-[#08111f]/70 px-3 py-3" }, creator.featured_cards_count || 0, " featured"), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-[#08111f]/70 px-3 py-3" }, creator.total_views_count || 0, " views")), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Feature on editorial page"), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(creator.nova_featured_creator), onChange: (event) => updateCreator(creator.id, { nova_featured_creator: event.target.checked }) })))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Categories"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, categories.map((category) => /* @__PURE__ */ React.createElement("div", { key: category.id, className: "rounded-2xl border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, category.name), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, category.slug, " • ", category.cards_count, " cards")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => saveCategory(category), className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-white transition hover:bg-white/[0.08]" }, "Save")))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Add category"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("input", { value: newCategory.name, onChange: (event) => setNewCategory((current) => ({ ...current, name: event.target.value })), placeholder: "Name", className: "w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: newCategory.slug, onChange: (event) => setNewCategory((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: newCategory.description, onChange: (event) => setNewCategory((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => saveCategory(newCategory), className: "w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, "Create category")))))); } -const __vite_glob_0_36 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_40 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NovaCardsAdminIndex }, Symbol.toStringTag, { value: "Module" })); @@ -72726,7 +76673,7 @@ function requestJson$e(url, { method = "GET", body: body2 } = {}) { }); } function NovaCardsAssetPackAdmin() { - const { props } = X(); + const { props } = X$1(); const [packs, setPacks] = React.useState(props.packs || []); const [selectedId, setSelectedId] = React.useState(null); const [form, setForm] = React.useState({ slug: "", name: "", description: "", type: "asset", preview_image: "", manifest_json: {}, official: true, active: true, order_num: 0 }); @@ -72760,14 +76707,14 @@ function NovaCardsAssetPackAdmin() { setSelectedId(response.pack.id); } } - return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Nova Cards Asset Packs" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "V2 pack system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official asset and template packs"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Control the official packs exposed in the v2 editor and public pack directories.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New pack"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing packs"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, packs.map((pack) => /* @__PURE__ */ React.createElement("button", { key: pack.id, type: "button", onClick: () => loadPack(pack), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === pack.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, pack.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, pack.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, pack.type)), pack.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, pack.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Pack editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Pack name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.type, onChange: (val) => setForm((current) => ({ ...current, type: val })), searchable: false, options: [{ value: "asset", label: "asset" }, { value: "template", label: "template" }] })), /* @__PURE__ */ React.createElement("input", { value: form.preview_image, onChange: (event) => setForm((current) => ({ ...current, preview_image: event.target.value })), placeholder: "Preview image URL", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: JSON.stringify(form.manifest_json || {}, null, 2), onChange: (event) => { + return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Nova Cards Asset Packs" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "V2 pack system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official asset and template packs"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Control the official packs exposed in the v2 editor and public pack directories.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New pack"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing packs"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, packs.map((pack) => /* @__PURE__ */ React.createElement("button", { key: pack.id, type: "button", onClick: () => loadPack(pack), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === pack.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, pack.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, pack.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, pack.type)), pack.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, pack.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Pack editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Pack name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.type, onChange: (val) => setForm((current) => ({ ...current, type: val })), searchable: false, options: [{ value: "asset", label: "asset" }, { value: "template", label: "template" }] })), /* @__PURE__ */ React.createElement("input", { value: form.preview_image, onChange: (event) => setForm((current) => ({ ...current, preview_image: event.target.value })), placeholder: "Preview image URL", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: JSON.stringify(form.manifest_json || {}, null, 2), onChange: (event) => { try { setForm((current) => ({ ...current, manifest_json: JSON.parse(event.target.value || "{}") })); } catch { } }, rows: 10, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 font-mono text-sm text-white md:col-span-2" })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.active), onChange: (event) => setForm((current) => ({ ...current, active: event.target.checked })), label: "Active" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: savePack, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update pack" : "Create pack")))); } -const __vite_glob_0_37 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_41 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NovaCardsAssetPackAdmin }, Symbol.toStringTag, { value: "Module" })); @@ -72789,7 +76736,7 @@ function requestJson$d(url, { method = "GET", body: body2 } = {}) { }); } function NovaCardsChallengeAdmin() { - const { props } = X(); + const { props } = X$1(); const [challenges, setChallenges] = React.useState(props.challenges || []); const [selectedId, setSelectedId] = React.useState(null); const [form, setForm] = React.useState({ slug: "", title: "", description: "", prompt: "", rules_json: {}, status: "draft", official: true, featured: false, winner_card_id: "", starts_at: "", ends_at: "" }); @@ -72826,14 +76773,14 @@ function NovaCardsChallengeAdmin() { setSelectedId(response.challenge.id); } } - return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Nova Cards Challenges" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Challenge system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Nova Cards challenge programming"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Program official challenge prompts, track featured runs, and connect winner cards.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New challenge"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing challenges"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, challenges.map((challenge) => /* @__PURE__ */ React.createElement("button", { key: challenge.id, type: "button", onClick: () => loadChallenge(challenge), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === challenge.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, challenge.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, challenge.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, challenge.status)), challenge.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, challenge.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Challenge editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.title, onChange: (event) => setForm((current) => ({ ...current, title: event.target.value })), placeholder: "Title", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("textarea", { value: form.prompt, onChange: (event) => setForm((current) => ({ ...current, prompt: event.target.value })), placeholder: "Prompt", rows: 4, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Status"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.status, onChange: (val) => setForm((current) => ({ ...current, status: val })), searchable: false, options: ["draft", "active", "completed", "archived"].map((s2) => ({ value: s2, label: s2 })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Winner card"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(form.winner_card_id || ""), onChange: (val) => setForm((current) => ({ ...current, winner_card_id: val })), placeholder: "No winner", options: cards.map((c) => ({ value: String(c.id), label: c.title })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Starts at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.starts_at, onChange: (nextValue) => setForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Starts at", clearable: true, className: "bg-[#0d1726]" })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Ends at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.ends_at, onChange: (nextValue) => setForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "Ends at", clearable: true, className: "bg-[#0d1726]" })), /* @__PURE__ */ React.createElement("textarea", { value: JSON.stringify(form.rules_json || {}, null, 2), onChange: (event) => { + return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Nova Cards Challenges" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Challenge system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Nova Cards challenge programming"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Program official challenge prompts, track featured runs, and connect winner cards.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New challenge"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing challenges"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, challenges.map((challenge) => /* @__PURE__ */ React.createElement("button", { key: challenge.id, type: "button", onClick: () => loadChallenge(challenge), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === challenge.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, challenge.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, challenge.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, challenge.status)), challenge.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, challenge.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Challenge editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.title, onChange: (event) => setForm((current) => ({ ...current, title: event.target.value })), placeholder: "Title", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("textarea", { value: form.prompt, onChange: (event) => setForm((current) => ({ ...current, prompt: event.target.value })), placeholder: "Prompt", rows: 4, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Status"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.status, onChange: (val) => setForm((current) => ({ ...current, status: val })), searchable: false, options: ["draft", "active", "completed", "archived"].map((s2) => ({ value: s2, label: s2 })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Winner card"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(form.winner_card_id || ""), onChange: (val) => setForm((current) => ({ ...current, winner_card_id: val })), placeholder: "No winner", options: cards.map((c) => ({ value: String(c.id), label: c.title })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Starts at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.starts_at, onChange: (nextValue) => setForm((current) => ({ ...current, starts_at: nextValue })), placeholder: "Starts at", clearable: true, className: "bg-[#0d1726]" })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Ends at"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.ends_at, onChange: (nextValue) => setForm((current) => ({ ...current, ends_at: nextValue })), placeholder: "Ends at", clearable: true, className: "bg-[#0d1726]" })), /* @__PURE__ */ React.createElement("textarea", { value: JSON.stringify(form.rules_json || {}, null, 2), onChange: (event) => { try { setForm((current) => ({ ...current, rules_json: JSON.parse(event.target.value || "{}") })); } catch { } }, rows: 10, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 font-mono text-sm text-white md:col-span-2" })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.featured), onChange: (event) => setForm((current) => ({ ...current, featured: event.target.checked })), label: "Featured" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveChallenge, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update challenge" : "Create challenge")))); } -const __vite_glob_0_38 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_42 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NovaCardsChallengeAdmin }, Symbol.toStringTag, { value: "Module" })); @@ -72855,7 +76802,7 @@ function requestJson$c(url, { method = "GET", body: body2 } = {}) { }); } function NovaCardsCollectionAdmin() { - const { props } = X(); + const { props } = X$1(); const [collections, setCollections] = React.useState(props.collections || []); const [selectedId, setSelectedId] = React.useState(props.collections?.[0]?.id || null); const [cardId, setCardId] = React.useState(""); @@ -72916,9 +76863,9 @@ function NovaCardsCollectionAdmin() { ); setCollections((current) => current.map((entry) => entry.id === collectionId ? response.collection : entry)); } - return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Nova Cards Collections" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Editorial layer"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official and public card collections"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Create editorial collections, assign owners, and curate the public card sets that the v2 browse surface links to.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedId(null), className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New collection"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Collections"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, collections.map((collection) => /* @__PURE__ */ React.createElement("button", { key: collection.id, type: "button", onClick: () => setSelectedId(collection.id), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === collection.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, collection.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, collection.featured ? "Featured • " : "", collection.official ? "Official" : "@" + (collection.owner?.username || "creator"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, collection.cards_count, " cards")), collection.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, collection.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Collection editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Owner"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.user_id, onChange: (val) => setForm((current) => ({ ...current, user_id: Number(val) })), options: admins.map((a) => ({ value: a.id, label: a.name || a.username })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Visibility"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.visibility, onChange: (val) => setForm((current) => ({ ...current, visibility: val })), searchable: false, options: [{ value: "public", label: "public" }, { value: "private", label: "private" }] })), /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Collection name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 4, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-4" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official collection" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.featured), onChange: (event) => setForm((current) => ({ ...current, featured: event.target.checked })), label: "Featured collection" })), selected?.public_url ? /* @__PURE__ */ React.createElement("a", { href: selected.public_url, className: "text-sky-100 transition hover:text-white", target: "_blank", rel: "noreferrer" }, "Open public page") : null), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveCollection, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update collection" : "Create collection")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Curate cards"), !selectedId ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/12 bg-white/[0.03] px-4 py-8 text-center text-sm text-slate-400" }, "Create or select a collection first.") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(cardId || ""), onChange: (val) => setCardId(val), placeholder: "Select a card", options: cards.map((c) => ({ value: String(c.id), label: c.title })) }), /* @__PURE__ */ React.createElement("input", { value: cardNote, onChange: (event) => setCardNote(event.target.value), placeholder: "Optional curator note", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: attachCard, className: "rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, "Add")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, (selected?.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "flex items-start justify-between gap-4 rounded-[22px] border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold text-white" }, item.card?.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, "#", item.sort_order, " ", item.card?.creator?.username ? `• @${item.card.creator.username}` : ""), item.note ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, item.note) : null), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => detachCard(selectedId, item.card.id), className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/15" }, "Remove"))))))))); + return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Nova Cards Collections" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Editorial layer"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official and public card collections"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Create editorial collections, assign owners, and curate the public card sets that the v2 browse surface links to.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedId(null), className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New collection"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Collections"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, collections.map((collection) => /* @__PURE__ */ React.createElement("button", { key: collection.id, type: "button", onClick: () => setSelectedId(collection.id), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === collection.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, collection.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, collection.featured ? "Featured • " : "", collection.official ? "Official" : "@" + (collection.owner?.username || "creator"))), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, collection.cards_count, " cards")), collection.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, collection.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Collection editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Owner"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.user_id, onChange: (val) => setForm((current) => ({ ...current, user_id: Number(val) })), options: admins.map((a) => ({ value: a.id, label: a.name || a.username })) })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Visibility"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.visibility, onChange: (val) => setForm((current) => ({ ...current, visibility: val })), searchable: false, options: [{ value: "public", label: "public" }, { value: "private", label: "private" }] })), /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Collection name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 4, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-4" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official collection" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.featured), onChange: (event) => setForm((current) => ({ ...current, featured: event.target.checked })), label: "Featured collection" })), selected?.public_url ? /* @__PURE__ */ React.createElement("a", { href: selected.public_url, className: "text-sky-100 transition hover:text-white", target: "_blank", rel: "noreferrer" }, "Open public page") : null), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveCollection, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update collection" : "Create collection")), /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Curate cards"), !selectedId ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/12 bg-white/[0.03] px-4 py-8 text-center text-sm text-slate-400" }, "Create or select a collection first.") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(cardId || ""), onChange: (val) => setCardId(val), placeholder: "Select a card", options: cards.map((c) => ({ value: String(c.id), label: c.title })) }), /* @__PURE__ */ React.createElement("input", { value: cardNote, onChange: (event) => setCardNote(event.target.value), placeholder: "Optional curator note", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: attachCard, className: "rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, "Add")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, (selected?.items || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "flex items-start justify-between gap-4 rounded-[22px] border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold text-white" }, item.card?.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, "#", item.sort_order, " ", item.card?.creator?.username ? `• @${item.card.creator.username}` : ""), item.note ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, item.note) : null), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => detachCard(selectedId, item.card.id), className: "rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/15" }, "Remove"))))))))); } -const __vite_glob_0_39 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_43 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NovaCardsCollectionAdmin }, Symbol.toStringTag, { value: "Module" })); @@ -72940,7 +76887,7 @@ function requestJson$b(url, { method = "GET", body: body2 } = {}) { }); } function NovaCardsTemplateAdmin() { - const { props } = X(); + const { props } = X$1(); const [templates, setTemplates] = React.useState(props.templates || []); const [selectedId, setSelectedId] = React.useState(null); const [form, setForm] = React.useState({ @@ -73021,9 +76968,9 @@ function NovaCardsTemplateAdmin() { }; }); } - return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Nova Cards Templates" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Template system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official Nova Cards templates"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Keep starter templates config-driven so the editor and render pipeline stay aligned as new card styles ship.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New template"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,1.4fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing templates"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, templates.map((template) => /* @__PURE__ */ React.createElement("button", { key: template.id, type: "button", onClick: () => loadTemplate(template), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === template.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, template.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, template.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, template.supported_formats?.join(", "))), template.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, template.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Template editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Template name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Font preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.font_preset || "modern-sans", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, font_preset: val } })), options: fonts.map((f2) => ({ value: f2.key, label: f2.label })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Gradient preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.gradient_preset || "midnight-nova", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, gradient_preset: val } })), options: gradients.map((g2) => ({ value: g2.key, label: g2.label })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Layout preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.layout || "quote_heavy", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, layout: val } })), options: ["quote_heavy", "author_emphasis", "centered", "minimal"].map((v) => ({ value: v, label: v })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Text alignment"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.text_align || "center", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, text_align: val } })), options: ["left", "center", "right"].map((v) => ({ value: v, label: v })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Overlay style"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.overlay_style || "dark-soft", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, overlay_style: val } })), options: ["none", "dark-soft", "dark-strong", "light-soft"].map((v) => ({ value: v, label: v })), searchable: false })), /* @__PURE__ */ React.createElement("label", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Text color"), /* @__PURE__ */ React.createElement("input", { type: "color", value: form.config_json?.text_color || "#ffffff", onChange: (event) => setForm((current) => ({ ...current, config_json: { ...current.config_json, text_color: event.target.value } })), className: "h-12 w-full rounded-2xl border border-white/10 bg-[#0d1726] p-2" }))), /* @__PURE__ */ React.createElement("div", { className: "mt-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-sm font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Supported formats"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, formats2.map((format) => /* @__PURE__ */ React.createElement("div", { key: format.key, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: form.supported_formats.includes(format.key), onChange: () => toggleFormat(format.key), label: format.label }))))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.active), onChange: (event) => setForm((current) => ({ ...current, active: event.target.checked })), label: "Active" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveTemplate, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update template" : "Create template")))); + return /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Nova Cards Templates" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Template system"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Official Nova Cards templates"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm leading-7 text-slate-300" }, "Keep starter templates config-driven so the editor and render pipeline stay aligned as new card styles ship.")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetForm, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "New template"), /* @__PURE__ */ React.createElement(xe, { href: endpoints.cards || "/cp/cards", className: "rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Back to cards")))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 grid gap-6 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,1.4fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Existing templates"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, templates.map((template) => /* @__PURE__ */ React.createElement("button", { key: template.id, type: "button", onClick: () => loadTemplate(template), className: `w-full rounded-[22px] border p-4 text-left transition ${selectedId === template.id ? "border-sky-300/35 bg-sky-400/10" : "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.05]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tracking-[-0.03em] text-white" }, template.name), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, template.slug)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-200" }, template.supported_formats?.join(", "))), template.description ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, template.description) : null)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Template editor"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), placeholder: "Template name", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), placeholder: "Slug", className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white" }), /* @__PURE__ */ React.createElement("textarea", { value: form.description, onChange: (event) => setForm((current) => ({ ...current, description: event.target.value })), placeholder: "Description", rows: 3, className: "rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white md:col-span-2" }), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Font preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.font_preset || "modern-sans", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, font_preset: val } })), options: fonts.map((f2) => ({ value: f2.key, label: f2.label })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Gradient preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.gradient_preset || "midnight-nova", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, gradient_preset: val } })), options: gradients.map((g2) => ({ value: g2.key, label: g2.label })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Layout preset"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.layout || "quote_heavy", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, layout: val } })), options: ["quote_heavy", "author_emphasis", "centered", "minimal"].map((v2) => ({ value: v2, label: v2 })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Text alignment"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.text_align || "center", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, text_align: val } })), options: ["left", "center", "right"].map((v2) => ({ value: v2, label: v2 })), searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Overlay style"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.config_json?.overlay_style || "dark-soft", onChange: (val) => setForm((current) => ({ ...current, config_json: { ...current.config_json, overlay_style: val } })), options: ["none", "dark-soft", "dark-strong", "light-soft"].map((v2) => ({ value: v2, label: v2 })), searchable: false })), /* @__PURE__ */ React.createElement("label", { className: "text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Text color"), /* @__PURE__ */ React.createElement("input", { type: "color", value: form.config_json?.text_color || "#ffffff", onChange: (event) => setForm((current) => ({ ...current, config_json: { ...current.config_json, text_color: event.target.value } })), className: "h-12 w-full rounded-2xl border border-white/10 bg-[#0d1726] p-2" }))), /* @__PURE__ */ React.createElement("div", { className: "mt-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-sm font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Supported formats"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, formats2.map((format) => /* @__PURE__ */ React.createElement("div", { key: format.key, className: "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: form.supported_formats.includes(format.key), onChange: () => toggleFormat(format.key), label: format.label }))))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.active), onChange: (event) => setForm((current) => ({ ...current, active: event.target.checked })), label: "Active" }), /* @__PURE__ */ React.createElement(Checkbox, { checked: Boolean(form.official), onChange: (event) => setForm((current) => ({ ...current, official: event.target.checked })), label: "Official" })), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveTemplate, className: "mt-5 w-full rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, selectedId ? "Update template" : "Create template")))); } -const __vite_glob_0_40 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_44 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NovaCardsTemplateAdmin }, Symbol.toStringTag, { value: "Module" })); @@ -73089,7 +77036,7 @@ function formatDateTime$1(value) { return date.toLocaleString(); } function SavedCollections() { - const { props } = X(); + const { props } = X$1(); const seo = props.seo || {}; const initialCollections = Array.isArray(props.collections) ? props.collections : []; const recentlyRevisited = Array.isArray(props.recentlyRevisited) ? props.recentlyRevisited : []; @@ -73297,7 +77244,7 @@ function SavedCollections() { "aria-label": `Move ${collection.title} down` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `reorder-${collection.id}` ? "fa-circle-notch fa-spin" : "fa-arrow-down"} fa-fw` }) - )) : null), savedLists.length ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 rounded-[24px] border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(selectedLists[collection.id] || savedLists[0]?.id || ""), onChange: (val) => setSelectedLists((current) => ({ ...current, [collection.id]: val })), options: savedLists.map((l) => ({ value: String(l.id), label: l.title })) }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleAddToList(collection.id), disabled: busy === `list-${collection.id}`, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.07] disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `list-${collection.id}` ? "fa-circle-notch fa-spin" : "fa-folder-plus"} fa-fw` }), "Add to list")) : null, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.04] px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Private Note"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleSaveNote(collection.id), disabled: busy === `note-${collection.id}`, className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-xs font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `note-${collection.id}` ? "fa-circle-notch fa-spin" : "fa-note-sticky"} fa-fw` }), "Save note")), /* @__PURE__ */ React.createElement( + )) : null), savedLists.length ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 rounded-[24px] border border-white/10 bg-white/[0.04] px-4 py-3" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(selectedLists[collection.id] || savedLists[0]?.id || ""), onChange: (val) => setSelectedLists((current) => ({ ...current, [collection.id]: val })), options: savedLists.map((l3) => ({ value: String(l3.id), label: l3.title })) }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleAddToList(collection.id), disabled: busy === `list-${collection.id}`, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.07] disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `list-${collection.id}` ? "fa-circle-notch fa-spin" : "fa-folder-plus"} fa-fw` }), "Add to list")) : null, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.04] px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Private Note"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => handleSaveNote(collection.id), disabled: busy === `note-${collection.id}`, className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-xs font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-60" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${busy === `note-${collection.id}` ? "fa-circle-notch fa-spin" : "fa-note-sticky"} fa-fw` }), "Save note")), /* @__PURE__ */ React.createElement( "textarea", { value: notes[collection.id] || "", @@ -73309,7 +77256,7 @@ function SavedCollections() { } ))))) : activeFilters.q || activeFilters.filter !== "all" || activeFilters.sort !== "saved_desc" || activeFilters.list ? /* @__PURE__ */ React.createElement("div", { className: "rounded-[32px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-16 text-center text-sm text-slate-300" }, "No saved collections match the current search or filters.") : /* @__PURE__ */ React.createElement(EmptyState$3, { browseUrl })), recommendedCollections.length ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80" }, "Recommended Next"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Because of what you save")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, recommendedCollections.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid grid-cols-1 gap-5 xl:grid-cols-3" }, recommendedCollections.map((collection) => /* @__PURE__ */ React.createElement(CollectionCard, { key: collection.id, collection, isOwner: false })))) : null))))); } -const __vite_glob_0_41 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_45 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: SavedCollections }, Symbol.toStringTag, { value: "Module" })); @@ -73647,7 +77594,7 @@ if (typeof document !== "undefined") { clientExports.createRoot(mountEl).render(/* @__PURE__ */ React.createElement(CommunityActivityPage, { ...props })); } } -const __vite_glob_0_42 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_46 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CommunityActivityPage }, Symbol.toStringTag, { value: "Module" })); @@ -73965,7 +77912,7 @@ if (typeof document !== "undefined") { clientExports.createRoot(mountEl).render(/* @__PURE__ */ React.createElement(LatestCommentsPage, { ...props })); } } -const __vite_glob_0_43 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_47 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: LatestCommentsPage }, Symbol.toStringTag, { value: "Module" })); @@ -74048,7 +77995,7 @@ function PostActions({ ), /* @__PURE__ */ React.createElement("div", { className: "relative ml-auto" }, /* @__PURE__ */ React.createElement( "button", { - onClick: () => setMenuOpen((v) => !v), + onClick: () => setMenuOpen((v2) => !v2), className: "flex items-center gap-1 px-3 py-1.5 rounded-lg text-sm hover:bg-white/5 hover:text-slate-200 transition-colors", "aria-label": "More options" }, @@ -74083,9 +78030,9 @@ function formatRelative$1(isoString) { const diff = Date.now() - new Date(isoString).getTime(); const s2 = Math.floor(diff / 1e3); if (s2 < 60) return "just now"; - const m = Math.floor(s2 / 60); - if (m < 60) return `${m}m ago`; - const h = Math.floor(m / 60); + const m2 = Math.floor(s2 / 60); + if (m2 < 60) return `${m2}m ago`; + const h = Math.floor(m2 / 60); if (h < 24) return `${h}h ago`; const d2 = Math.floor(h / 24); return `${d2}d ago`; @@ -74245,16 +78192,16 @@ const ICONS = { private: { icon: "fa-lock", label: "Private", cls: "text-amber-500/70" } }; function VisibilityPill({ visibility, showLabel = false }) { - const v = ICONS[visibility] ?? ICONS.public; + const v2 = ICONS[visibility] ?? ICONS.public; return /* @__PURE__ */ React.createElement( "span", { - className: `inline-flex items-center gap-1 text-xs ${v.cls}`, - title: v.label, - "aria-label": `Visibility: ${v.label}` + className: `inline-flex items-center gap-1 text-xs ${v2.cls}`, + title: v2.label, + "aria-label": `Visibility: ${v2.label}` }, - /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v.icon} fa-fw` }), - showLabel && /* @__PURE__ */ React.createElement("span", null, v.label) + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v2.icon} fa-fw` }), + showLabel && /* @__PURE__ */ React.createElement("span", null, v2.label) ); } function LinkPreviewCard({ preview, onDismiss, loading = false }) { @@ -74309,9 +78256,9 @@ function formatRelative(isoString) { const diff = Date.now() - new Date(isoString).getTime(); const s2 = Math.floor(diff / 1e3); if (s2 < 60) return "just now"; - const m = Math.floor(s2 / 60); - if (m < 60) return `${m}m ago`; - const h = Math.floor(m / 60); + const m2 = Math.floor(s2 / 60); + if (m2 < 60) return `${m2}m ago`; + const h = Math.floor(m2 / 60); if (h < 24) return `${h}h ago`; const d2 = Math.floor(h / 24); return `${d2}d ago`; @@ -74445,7 +78392,7 @@ function PostCard$1({ post: post2, isLoggedIn = false, viewerUsername = null, on ), isOwn && /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement( "button", { - onClick: () => setMenuOpen((v) => !v), + onClick: () => setMenuOpen((v2) => !v2), className: "text-slate-500 hover:text-slate-300 px-2 py-1 rounded-lg hover:bg-white/5 transition-colors", "aria-label": "Post options" }, @@ -74518,7 +78465,7 @@ function PostCard$1({ post: post2, isLoggedIn = false, viewerUsername = null, on { post: postData, isLoggedIn, - onCommentToggle: () => setShowComments((v) => !v), + onCommentToggle: () => setShowComments((v2) => !v2), onReactionChange: ({ liked, count }) => setPostData((p) => ({ ...p, viewer_liked: liked, reactions_count: count })) } )), showComments && /* @__PURE__ */ React.createElement("div", { className: "border-t border-white/[0.04] px-5 py-4" }, /* @__PURE__ */ React.createElement( @@ -74565,7 +78512,7 @@ function EmptyFollowingState() { return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center justify-center py-24 text-center" }, /* @__PURE__ */ React.createElement("div", { className: "w-20 h-20 rounded-2xl bg-white/5 flex items-center justify-center mb-5 text-slate-600" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-users text-3xl" })), /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white/80 mb-2" }, "Nothing here yet"), /* @__PURE__ */ React.createElement("p", { className: "text-slate-500 text-sm max-w-sm leading-relaxed" }, "Follow some creators to see their posts here. Discover amazing artwork on", " ", /* @__PURE__ */ React.createElement("a", { href: "/discover/trending", className: "text-sky-400 hover:underline" }, "Trending"), ".")); } function FollowingFeed() { - const { props } = X(); + const { props } = X$1(); const { auth, seo } = props; const authUser = auth?.user ?? null; const [posts, setPosts] = reactExports.useState([]); @@ -74636,12 +78583,12 @@ function FollowingFeed() { loading ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-spinner fa-spin mr-2" }), "Loading…") : "Load more" ))))); } -const __vite_glob_0_44 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_48 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: FollowingFeed }, Symbol.toStringTag, { value: "Module" })); function HashtagFeed() { - const { props } = X(); + const { props } = X$1(); const { auth, tag, seo } = props; const authUser = auth?.user ?? null; const [posts, setPosts] = reactExports.useState([]); @@ -74696,12 +78643,12 @@ function HashtagFeed() { loading ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-spinner fa-spin mr-2" }), "Loading…") : "Load more" )))))); } -const __vite_glob_0_45 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_49 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: HashtagFeed }, Symbol.toStringTag, { value: "Module" })); function SavedFeed() { - const { props } = X(); + const { props } = X$1(); const { auth, seo } = props; const authUser = auth?.user ?? null; const [posts, setPosts] = reactExports.useState([]); @@ -74754,7 +78701,7 @@ function SavedFeed() { loading ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-spinner fa-spin mr-2" }), "Loading…") : "Load more" )))))); } -const __vite_glob_0_46 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_50 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: SavedFeed }, Symbol.toStringTag, { value: "Module" })); @@ -74772,7 +78719,7 @@ function TrendingHashtagsSidebar$1({ hashtags }) { )))); } function SearchFeed() { - const { props } = X(); + const { props } = X$1(); const { auth, initialQuery, trendingHashtags, seo } = props; const authUser = auth?.user ?? null; const [query, setQuery] = reactExports.useState(initialQuery ?? ""); @@ -74879,7 +78826,7 @@ function SearchFeed() { "Load more" ))), /* @__PURE__ */ React.createElement("aside", { className: "hidden lg:block w-64 shrink-0 space-y-4 pt-14" }, /* @__PURE__ */ React.createElement(TrendingHashtagsSidebar$1, { hashtags: trendingHashtags }), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/[0.07] bg-white/[0.03] px-4 py-4 text-center" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 leading-relaxed" }, "Tip: search ", /* @__PURE__ */ React.createElement("span", { className: "text-sky-400/80" }, "#hashtag"), " to find posts by topic."))))))); } -const __vite_glob_0_47 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_51 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: SearchFeed }, Symbol.toStringTag, { value: "Module" })); @@ -74897,7 +78844,7 @@ function TrendingHashtagsSidebar({ hashtags, activeTag = null }) { )))); } function TrendingFeed() { - const { props } = X(); + const { props } = X$1(); const { auth, trendingHashtags, seo } = props; const authUser = auth?.user ?? null; const [posts, setPosts] = reactExports.useState([]); @@ -74941,7 +78888,7 @@ function TrendingFeed() { loading ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-spinner fa-spin mr-2" }), "Loading…") : "Load more" ))), /* @__PURE__ */ React.createElement("aside", { className: "hidden lg:block w-64 shrink-0 space-y-4 pt-14" }, /* @__PURE__ */ React.createElement(TrendingHashtagsSidebar, { hashtags: trendingHashtags }), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/[0.07] bg-white/[0.03] px-4 py-4 text-center" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 leading-relaxed" }, "Posts are ranked by likes, comments & engagement over the last 7 days."))))))); } -const __vite_glob_0_48 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_52 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: TrendingFeed }, Symbol.toStringTag, { value: "Module" })); @@ -75026,7 +78973,7 @@ function ForumCategory({ category, parentCategory = null, threads = [], paginati "New topic" ))), /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-2xl border border-white/[0.06] bg-nova-800/50 backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-4 border-b border-white/[0.06] px-5 py-3" }, /* @__PURE__ */ React.createElement("span", { className: "flex-1 text-xs font-semibold uppercase tracking-widest text-white/30" }, "Topics"), /* @__PURE__ */ React.createElement("span", { className: "w-16 text-center text-xs font-semibold uppercase tracking-widest text-white/30" }, "Replies")), threads.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "px-5 py-12 text-center" }, /* @__PURE__ */ React.createElement("svg", { className: "mx-auto mb-4 text-zinc-600", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" })), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-zinc-500" }, "No topics in this board yet."), isAuthenticated && slug && /* @__PURE__ */ React.createElement("a", { href: `/forum/${slug}/new`, className: "mt-3 inline-block text-sm text-sky-300 hover:text-sky-200" }, "Be the first to start a discussion →")) : /* @__PURE__ */ React.createElement("div", null, threads.map((thread, i) => /* @__PURE__ */ React.createElement(ThreadRow, { key: thread.topic_id ?? thread.id ?? i, thread, isFirst: i === 0 })))), pagination?.last_page > 1 && /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React.createElement(Pagination$1, { meta: pagination })))); } -const __vite_glob_0_49 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_53 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumCategory }, Symbol.toStringTag, { value: "Module" })); @@ -75290,7 +79237,7 @@ function ForumEditPost({ post: post2, thread, csrfToken: csrfToken2, errors = {} ), /* @__PURE__ */ React.createElement(Button$1, { type: "submit", variant: "primary", size: "md", loading: submitting }, "Save changes")) ))); } -const __vite_glob_0_50 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_54 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumEditPost }, Symbol.toStringTag, { value: "Module" })); @@ -75381,7 +79328,7 @@ function formatLastActivity(value) { } return `Updated ${date.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" })}`; } -const __vite_glob_0_51 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_55 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumIndex }, Symbol.toStringTag, { value: "Module" })); @@ -75502,7 +79449,7 @@ function ForumNewThread({ category, csrfToken: csrfToken2, errors = {}, oldValue /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between pt-2" }, /* @__PURE__ */ React.createElement("a", { href: `/forum/${slug}`, className: "text-sm text-zinc-500 hover:text-zinc-300 transition-colors" }, "← Cancel"), /* @__PURE__ */ React.createElement(Button$1, { type: "submit", variant: "primary", size: "md", loading: submitting }, "Publish topic")) ))); } -const __vite_glob_0_52 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_56 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumNewThread }, Symbol.toStringTag, { value: "Module" })); @@ -75515,9 +79462,9 @@ function ForumSection({ category, boards = [], seo = {} }) { { label: "Forum", href: "/forum" }, { label: name2 } ]; - return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl px-4 pb-20 pt-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Breadcrumbs, { items: breadcrumbs }), /* @__PURE__ */ React.createElement("section", { className: "mt-5 overflow-hidden rounded-3xl border border-white/10 bg-nova-800/55 shadow-xl backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "relative h-56 overflow-hidden sm:h-64" }, /* @__PURE__ */ React.createElement("img", { src: preview, alt: `${name2} preview`, className: "h-full w-full object-cover object-center" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-0 bottom-0 p-6 sm:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-[0.18em] text-cyan-200/85" }, "Forum Section"), /* @__PURE__ */ React.createElement("h1", { className: "mt-2 text-3xl font-black text-white sm:text-4xl" }, name2), description && /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm text-white/70 sm:text-base" }, description)))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-2xl border border-white/8 bg-nova-800/45 p-5 backdrop-blur sm:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-end justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-[0.16em] text-white/40" }, "Subcategories"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-bold text-white" }, "Browse boards")), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-white/45 sm:text-sm" }, "Select a board to open its thread list.")), boards.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "py-12 text-center text-sm text-white/45" }, "No boards are available in this section yet.") : /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 md:grid-cols-2" }, boards.map((board) => /* @__PURE__ */ React.createElement("a", { key: board.id ?? board.slug, href: `/forum/${board.slug}`, className: "rounded-2xl border border-white/8 bg-white/[0.02] p-5 transition hover:border-cyan-400/25 hover:bg-white/[0.04]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, board.title), board.description && /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white/55" }, board.description)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-cyan-300/20 bg-cyan-300/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-cyan-200" }, "Open")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-white/50" }, /* @__PURE__ */ React.createElement("span", null, board.topics_count ?? 0, " topics"), /* @__PURE__ */ React.createElement("span", null, board.posts_count ?? 0, " posts"), board.latest_topic?.title && /* @__PURE__ */ React.createElement("span", null, "Latest: ", board.latest_topic.title)))))))); + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl px-4 pb-20 pt-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(Breadcrumbs, { items: breadcrumbs }), /* @__PURE__ */ React.createElement("section", { className: "mt-5 overflow-hidden rounded-3xl border border-white/10 bg-nova-800/55 shadow-xl backdrop-blur" }, /* @__PURE__ */ React.createElement("div", { className: "relative h-56 overflow-hidden sm:h-64" }, /* @__PURE__ */ React.createElement("img", { src: preview, alt: `${name2} preview`, className: "h-full w-full object-cover object-center" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent" }), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-0 bottom-0 p-6 sm:p-8" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-[0.18em] text-cyan-200/85" }, "Forum Section"), /* @__PURE__ */ React.createElement("h1", { className: "mt-2 text-3xl font-black text-white sm:text-4xl" }, name2), description && /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm text-white/70 sm:text-base" }, description)))), /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-2xl border border-white/8 bg-nova-800/45 p-5 backdrop-blur sm:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-end justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-[0.16em] text-white/40" }, "Subcategories"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-2xl font-bold text-white" }, "Browse boards")), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-white/45 sm:text-sm" }, "Select a board to open its thread list.")), boards.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "py-12 text-center text-sm text-white/45" }, "No boards are available in this section yet.") : /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 md:grid-cols-2" }, boards.map((board) => /* @__PURE__ */ React.createElement("a", { key: board.id ?? board.slug, href: `/forum/${board.slug}`, className: "rounded-2xl border border-white/8 bg-white/[0.02] p-5 transition hover:border-cyan-400/25 hover:bg-white/[0.04] block" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, board.title), board.description && /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white/55" }, board.description)), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-cyan-300/20 bg-cyan-300/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-cyan-200" }, "Open")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-white/50" }, /* @__PURE__ */ React.createElement("span", null, board.topics_count ?? 0, " topics"), /* @__PURE__ */ React.createElement("span", null, board.posts_count ?? 0, " posts"), board.latest_topic?.title && /* @__PURE__ */ React.createElement("span", null, "Latest: ", board.latest_topic.title)))))))); } -const __vite_glob_0_53 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_57 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumSection }, Symbol.toStringTag, { value: "Module" })); @@ -75855,7 +79802,7 @@ function formatDate$b(dateStr) { return ""; } } -const __vite_glob_0_54 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_58 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ForumThread }, Symbol.toStringTag, { value: "Module" })); @@ -76092,24 +80039,24 @@ function OutcomeSection({ section }) { return /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, section.label), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 sm:grid-cols-2 xl:grid-cols-1" }, items.map((item) => /* @__PURE__ */ React.createElement(WorldChallengeArtworkCard, { key: item.id, item, featured: item.outcome_type === "winner" })))); } function GroupChallengeShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const challenge = props.challenge || {}; const linkedWorld = props.linkedWorld || null; const outcomeSections = challenge.outcome_sections || {}; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(234,179,8,0.15),_transparent_28%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: props.seo || {}, title: `${challenge.title || group.name} - Skinbase`, description: challenge.summary || challenge.description || "Group challenge" }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-white/[0.03]" }, challenge.cover_url ? /* @__PURE__ */ React.createElement("img", { src: challenge.cover_url, alt: challenge.title, className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "h-40 bg-white/[0.03]" }), /* @__PURE__ */ React.createElement("div", { className: "p-6" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "text-sm font-semibold text-amber-200" }, group.name), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold text-white" }, challenge.title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-7 text-slate-300" }, challenge.summary || challenge.description || "Group challenge"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3 text-xs uppercase tracking-[0.16em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, challenge.status), /* @__PURE__ */ React.createElement("span", null, challenge.visibility), /* @__PURE__ */ React.createElement("span", null, String(challenge.participation_scope || "").replace("_", " ")), challenge.start_at ? /* @__PURE__ */ React.createElement("span", null, "Starts ", new Date(challenge.start_at).toLocaleDateString()) : null, challenge.end_at ? /* @__PURE__ */ React.createElement("span", null, "Ends ", new Date(challenge.end_at).toLocaleDateString()) : null), /* @__PURE__ */ React.createElement(ChallengeWorldLinkBadge, { world: linkedWorld, className: "mt-5" }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Challenge brief"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, challenge.description || "No extended challenge brief yet."), challenge.rules_text ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Rules"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, challenge.rules_text)) : null, challenge.submission_instructions ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Submission instructions"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-7 text-slate-300" }, challenge.submission_instructions)) : null), /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement(OutcomeSection, { section: outcomeSections.winner }), /* @__PURE__ */ React.createElement(OutcomeSection, { section: outcomeSections.finalist }), /* @__PURE__ */ React.createElement(OutcomeSection, { section: outcomeSections.runner_up }), /* @__PURE__ */ React.createElement(OutcomeSection, { section: outcomeSections.honorable_mention }), /* @__PURE__ */ React.createElement(OutcomeSection, { section: outcomeSections.featured }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Entries"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 sm:grid-cols-2 xl:grid-cols-1" }, Array.isArray(challenge.artworks) && challenge.artworks.length > 0 ? challenge.artworks.map((artwork) => /* @__PURE__ */ React.createElement("a", { key: artwork.id, href: artwork.url, className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/20" }, artwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "aspect-[4/3] w-full object-cover" }) : null, /* @__PURE__ */ React.createElement("div", { className: "p-4 text-white" }, artwork.title))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No entries linked yet."))))))); } -const __vite_glob_0_55 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_59 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupChallengeShow }, Symbol.toStringTag, { value: "Module" })); function GroupEventShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const event = props.event || {}; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(16,185,129,0.15),_transparent_28%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: props.seo || {}, title: `${event.title || group.name} - Skinbase`, description: event.summary || event.description || "Group event" }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-5xl space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-white/[0.03]" }, event.cover_url ? /* @__PURE__ */ React.createElement("img", { src: event.cover_url, alt: event.title, className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "h-40 bg-white/[0.03]" }), /* @__PURE__ */ React.createElement("div", { className: "p-6" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "text-sm font-semibold text-emerald-200" }, group.name), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold text-white" }, event.title), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-7 text-slate-300" }, event.summary || event.description || "Group event"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Starts"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-white" }, event.start_at ? new Date(event.start_at).toLocaleString() : "Not scheduled")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Details"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-white" }, event.event_type, " • ", event.visibility), event.location ? /* @__PURE__ */ React.createElement("div", { className: "mt-2" }, event.location) : null)), event.external_url ? /* @__PURE__ */ React.createElement("a", { href: event.external_url, className: "mt-5 inline-flex rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Open external link") : null)), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "About this event"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, event.description || "No extended event details yet.")))); } -const __vite_glob_0_56 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_60 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupEventShow }, Symbol.toStringTag, { value: "Module" })); @@ -76730,7 +80677,7 @@ function FaqAnswer({ item, links }) { return /* @__PURE__ */ React.createElement(React.Fragment, null, Array.isArray(item.paragraphs) ? item.paragraphs.map((paragraph2) => /* @__PURE__ */ React.createElement("p", { key: paragraph2 }, paragraph2)) : null, Array.isArray(item.bullets) && item.bullets.length > 0 ? /* @__PURE__ */ React.createElement("ul", { className: "space-y-2" }, item.bullets.map((bullet) => /* @__PURE__ */ React.createElement("li", { key: bullet, className: "flex gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "mt-2 h-2 w-2 shrink-0 rounded-full bg-sky-300" }), /* @__PURE__ */ React.createElement("span", null, bullet)))) : null, Array.isArray(item.example) && item.example.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, item.example.map((entry) => /* @__PURE__ */ React.createElement("div", { key: entry.label, className: "rounded-[20px] border border-white/10 bg-white/[0.03] p-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, entry.label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm font-semibold text-white" }, entry.value)))) : null, Array.isArray(item.links) && item.links.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3 pt-1" }, item.links.map((link2) => /* @__PURE__ */ React.createElement("a", { key: link2.label, href: links[link2.linkKey] || "#", className: "text-sm font-semibold text-sky-200 underline underline-offset-4" }, link2.label))) : null); } function GroupFaqPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const [query, setQuery] = reactExports.useState(""); const normalizedQuery = query.trim().toLowerCase(); @@ -76799,7 +80746,7 @@ function GroupFaqPage() { /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("a", { href: links.contact_support, className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Contact"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Contact support"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, "Use this if your question is not answered here or if you need help with an account or workflow issue.")), /* @__PURE__ */ React.createElement("a", { href: links.report_issue, className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Report"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Report a problem"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, "Use this if a route, role, contributor record, or Group workflow appears broken rather than just unclear."))) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Support flow"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.quickstart, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Quickstart"), /* @__PURE__ */ React.createElement("a", { href: links.full_documentation, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read full documentation"), /* @__PURE__ */ React.createElement("a", { href: links.group_studio, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Group Studio"), /* @__PURE__ */ React.createElement("a", { href: links.create_group, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Create a Group"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Quick troubleshooting rule"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "If something feels wrong, check three things first: are you in the right Group context, do you have the right role, and is the content public or internal?"))))))); } -const __vite_glob_0_57 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_61 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupFaqPage }, Symbol.toStringTag, { value: "Module" })); @@ -77117,7 +81064,7 @@ function TroubleCard$6({ item }) { return /* @__PURE__ */ React.createElement("article", { className: "rounded-[28px] border border-white/10 bg-black/20 p-5" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body)); } function GroupHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const heroJsonLd = [ { @@ -77347,7 +81294,7 @@ function GroupHelpPage() { /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("a", { href: links.create_group, className: "rounded-[28px] border border-sky-300/20 bg-sky-300/10 p-5 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100/80" }, "Create"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Create your first Group"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-sky-50/80" }, "Start with branding, visibility, and your first member invites.")), /* @__PURE__ */ React.createElement("a", { href: links.group_studio, className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Manage"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Open Group Studio"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, "Check members, workflow, releases, recruitment, and review status.")), /* @__PURE__ */ React.createElement("a", { href: links.contact_support, className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Contact"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Contact support"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, "Use the general support flow if you need help untangling an account or workflow issue.")), /* @__PURE__ */ React.createElement("a", { href: links.report_issue, className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Report"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, "Report a problem"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, "Use this if a route, permission, credit record, or workflow appears broken."))) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick actions"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.groups_directory, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Browse public Groups"), /* @__PURE__ */ React.createElement("a", { href: links.group_studio, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Group Studio"), links.faq ? /* @__PURE__ */ React.createElement("a", { href: links.faq, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Groups FAQ") : null, /* @__PURE__ */ React.createElement("a", { href: "#publishing-as-a-group", className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Review publishing guidance"), /* @__PURE__ */ React.createElement("a", { href: "#contributor-credit", className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Check contributor credit rules"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Read this before launch day"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "Before the first public release or artwork, confirm the Group context, contributor credit, and review expectations. Those three checks prevent most avoidable confusion."))))))); } -const __vite_glob_0_58 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_62 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -77396,7 +81343,7 @@ function GroupLeaderboardCard({ item }) { return /* @__PURE__ */ React.createElement("article", { className: "rounded-[26px] border border-white/10 bg-white/[0.03] p-4 shadow-[0_18px_50px_rgba(2,6,23,0.3)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-white/10 bg-slate-950/70 text-lg font-black text-white" }, "#", item.rank), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("a", { href: entity.url || "/groups", className: "block truncate text-lg font-semibold text-white transition hover:text-sky-300" }, entity.name), entity.headline ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, entity.headline) : null), /* @__PURE__ */ React.createElement("div", { className: "text-right" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.18em] text-slate-500" }, "Score"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xl font-black text-white" }, Number(item.score || 0).toLocaleString()))), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, (Array.isArray(entity.trust_signals) ? entity.trust_signals : []).slice(0, 2).map((signal) => /* @__PURE__ */ React.createElement(GroupBadgePill, { key: signal.key, label: signal.label, tone: signal.tone })), entity.is_recruiting ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Recruiting", tone: "emerald" }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, Number(entity.artworks_count || 0).toLocaleString(), " artworks"), /* @__PURE__ */ React.createElement("span", null, Number(entity.members_count || 0).toLocaleString(), " members"), /* @__PURE__ */ React.createElement("span", null, Number(entity.followers_count || 0).toLocaleString(), " followers"))))); } function GroupIndex() { - const { props } = X(); + const { props } = X$1(); const groups = props.groups?.data || []; const surfaces = Array.isArray(props.surfaces) ? props.surfaces : []; const currentSurface = props.currentSurface || "featured"; @@ -77422,7 +81369,7 @@ function GroupIndex() { } )), leaderboardItems.length > 0 ? /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", { className: "mb-5 flex items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold tracking-[-0.02em] text-white" }, "Monthly group leaderboard"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, "A fast view of the collaborative teams moving the most attention and publishing energy right now.")), /* @__PURE__ */ React.createElement("a", { href: "/leaderboard?type=groups&period=monthly", className: "text-sm font-semibold text-sky-200 transition hover:text-white" }, "View leaderboard")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 xl:grid-cols-3" }, leaderboardItems.slice(0, 3).map((item) => /* @__PURE__ */ React.createElement(GroupLeaderboardCard, { key: item.entity?.id || item.rank, item })))) : null, /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", { className: "mb-5 flex items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold tracking-[-0.02em] text-white" }, "Browse groups"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, "Filter the directory by discovery surface, then jump into each group’s public page for artworks, releases, projects, events, and activity.")), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-500" }, Number(props.groups?.meta?.total || 0).toLocaleString(), " public groups")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, groups.map((group) => /* @__PURE__ */ React.createElement(GroupDiscoveryCard, { key: group.slug || group.id, group })))))); } -const __vite_glob_0_59 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_63 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupIndex }, Symbol.toStringTag, { value: "Module" })); @@ -77430,7 +81377,7 @@ function csrfToken$3() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || ""; } function GroupPostShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const post2 = props.post || {}; const recentPosts = Array.isArray(props.recentPosts) ? props.recentPosts : []; @@ -77452,7 +81399,7 @@ function GroupPostShow() { }; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_28%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: props.seo || {}, title: `${post2.title || group.name} - Skinbase`, description: post2.excerpt || group.headline || group.bio || "Group post" }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-5xl" }, /* @__PURE__ */ React.createElement("article", { className: "rounded-[32px] border border-white/10 bg-white/[0.03] p-6 sm:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "text-sm font-semibold text-sky-200" }, "← Back to ", group.name), props.reportEndpoint ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: submitReport, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-white" }, "Report") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2 text-xs uppercase tracking-[0.16em] text-slate-400" }, post2.type ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1" }, post2.type) : null, post2.is_pinned ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-3 py-1 text-amber-100" }, "Pinned") : null), /* @__PURE__ */ React.createElement("h1", { className: "mt-5 text-4xl font-semibold text-white" }, post2.title), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-sm text-slate-400" }, post2.author?.name || post2.author?.username || group.name, " • ", post2.published_at ? new Date(post2.published_at).toLocaleString() : "Recently"), post2.excerpt ? /* @__PURE__ */ React.createElement("p", { className: "mt-6 text-lg leading-8 text-slate-200" }, post2.excerpt) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-8 whitespace-pre-wrap text-sm leading-7 text-slate-300" }, post2.content || "")), recentPosts.length > 0 ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 rounded-[32px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "More from ", group.name), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, recentPosts.filter((item) => item.id !== post2.id).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.url, className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, item.type), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, item.excerpt || "Read the full post."))))) : null)); } -const __vite_glob_0_60 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_64 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupPostShow }, Symbol.toStringTag, { value: "Module" })); @@ -77463,12 +81410,12 @@ function ArtworkGrid$2({ artworks }) { return /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 sm:grid-cols-2 xl:grid-cols-3" }, artworks.map((artwork) => /* @__PURE__ */ React.createElement("a", { key: artwork.id, href: artwork.url, className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/20" }, artwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "aspect-[4/3] w-full object-cover" }) : null, /* @__PURE__ */ React.createElement("div", { className: "p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-base font-semibold text-white" }, artwork.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, artwork.author || "Artwork"))))); } function GroupProjectShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const project = props.project || {}; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_28%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: props.seo || {}, title: `${project.title || group.name} - Skinbase`, description: project.summary || project.description || group.headline || "Group project" }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-white/[0.03]" }, project.cover_url ? /* @__PURE__ */ React.createElement("img", { src: project.cover_url, alt: project.title, className: "h-56 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "h-40 bg-white/[0.03]" }), /* @__PURE__ */ React.createElement("div", { className: "p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "text-sm font-semibold text-sky-200" }, group.name), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, project.status), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, project.visibility)), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold text-white" }, project.title), project.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-7 text-slate-300" }, project.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-4 text-xs text-slate-400" }, project.start_date ? /* @__PURE__ */ React.createElement("span", null, "Started ", new Date(project.start_date).toLocaleDateString()) : null, project.target_date ? /* @__PURE__ */ React.createElement("span", null, "Target ", new Date(project.target_date).toLocaleDateString()) : null, project.released_at ? /* @__PURE__ */ React.createElement("span", null, "Released ", new Date(project.released_at).toLocaleDateString()) : null, project.lead?.name || project.lead?.username ? /* @__PURE__ */ React.createElement("span", null, "Lead: ", project.lead?.name || project.lead?.username) : null))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Overview"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, project.description || "No long-form description yet."), Array.isArray(project.milestones) && project.milestones.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3" }, project.milestones.map((milestone) => /* @__PURE__ */ React.createElement("div", { key: milestone.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, milestone.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, milestone.status)), milestone.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, milestone.summary) : null, milestone.owner?.name || milestone.owner?.username ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs text-slate-500" }, "Owner: ", milestone.owner?.name || milestone.owner?.username) : null))) : null, /* @__PURE__ */ React.createElement(ArtworkGrid$2, { artworks: project.artworks })), /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Pipeline"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-sm leading-7 text-slate-300" }, "This project currently has ", project.counts?.milestones || 0, " milestones and is linked to ", project.release_count || project.counts?.releases || 0, " releases.")), Array.isArray(project.assets) && project.assets.length > 0 ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Assets"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, project.assets.map((asset) => /* @__PURE__ */ React.createElement("a", { key: asset.id, href: asset.download_url, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold" }, asset.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, asset.category, " • ", asset.visibility))))) : null, Array.isArray(project.team) && project.team.length > 0 ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Team"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, project.team.map((member) => /* @__PURE__ */ React.createElement("div", { key: member.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold" }, member.name || member.username), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, member.role_label || (member.is_lead ? "Lead" : "Contributor")))))) : null, project.pinned_post ? /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Pinned update"), /* @__PURE__ */ React.createElement("a", { href: project.pinned_post.url, className: "mt-4 inline-block text-sm font-semibold text-sky-200" }, project.pinned_post.title)) : null)))); } -const __vite_glob_0_61 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_65 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupProjectShow }, Symbol.toStringTag, { value: "Module" })); @@ -77643,7 +81590,7 @@ function CreditCard({ item }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, item.label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-base font-semibold text-white" }, item.value), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-300" }, item.note)); } function GroupQuickstartPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const nextSteps = NEXT_STEPS.map((item) => ({ ...item, @@ -77767,7 +81714,7 @@ function GroupQuickstartPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: nextSteps }) ))))); } -const __vite_glob_0_62 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_66 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupQuickstartPage }, Symbol.toStringTag, { value: "Module" })); @@ -77778,14 +81725,14 @@ function ArtworkGrid$1({ artworks }) { return /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 sm:grid-cols-2 xl:grid-cols-3" }, artworks.map((artwork) => /* @__PURE__ */ React.createElement("a", { key: artwork.id, href: artwork.url, className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/20 transition hover:border-white/20" }, artwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: artwork.thumb, alt: artwork.title, className: "aspect-[4/3] w-full object-cover" }) : null, /* @__PURE__ */ React.createElement("div", { className: "p-4" }, /* @__PURE__ */ React.createElement("h3", { className: "text-base font-semibold text-white" }, artwork.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, artwork.author || "Artwork"))))); } function GroupReleaseShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const release = props.release || {}; const contributors = Array.isArray(release.contributors) ? release.contributors : []; const milestones = Array.isArray(release.milestones) ? release.milestones : []; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_28%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { seo: props.seo || {}, title: `${release.title || group.name} - Skinbase`, description: release.summary || release.description || group.headline || "Group release" }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-6xl space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[32px] border border-white/10 bg-white/[0.03]" }, release.cover_url ? /* @__PURE__ */ React.createElement("img", { src: release.cover_url, alt: release.title, className: "h-64 w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "h-44 bg-white/[0.03]" }), /* @__PURE__ */ React.createElement("div", { className: "p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "text-sm font-semibold text-sky-200" }, group.name), release.status ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, release.status) : null, release.current_stage ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, release.current_stage) : null), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold text-white" }, release.title), release.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-3xl text-sm leading-7 text-slate-300" }, release.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-4 text-xs text-slate-400" }, release.released_at ? /* @__PURE__ */ React.createElement("span", null, "Released ", new Date(release.released_at).toLocaleDateString()) : null, release.planned_release_at ? /* @__PURE__ */ React.createElement("span", null, "Planned ", new Date(release.planned_release_at).toLocaleDateString()) : null, release.lead?.name || release.lead?.username ? /* @__PURE__ */ React.createElement("span", null, "Lead: ", release.lead?.name || release.lead?.username) : null, /* @__PURE__ */ React.createElement("span", null, release.counts?.artworks || 0, " artworks"), /* @__PURE__ */ React.createElement("span", null, release.counts?.contributors || 0, " contributors"), /* @__PURE__ */ React.createElement("span", null, release.counts?.milestones || 0, " milestones")))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-8 xl:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Overview"), /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-7 text-slate-300" }, release.description || "No long-form release description yet."), release.release_notes ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Release notes"), /* @__PURE__ */ React.createElement("div", { className: "mt-3 whitespace-pre-wrap text-sm leading-7 text-slate-300" }, release.release_notes)) : null, /* @__PURE__ */ React.createElement(ArtworkGrid$1, { artworks: release.artworks })), /* @__PURE__ */ React.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Links"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, release.linked_project?.url ? /* @__PURE__ */ React.createElement("a", { href: release.linked_project.url, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold" }, release.linked_project.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, "Linked project")) : null, release.linked_collection?.url ? /* @__PURE__ */ React.createElement("a", { href: release.linked_collection.url, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold" }, release.linked_collection.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, "Linked collection")) : null, release.featured_artwork ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold" }, release.featured_artwork.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, "Featured artwork")) : null)), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Contributors"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, contributors.length > 0 ? contributors.map((contributor) => /* @__PURE__ */ React.createElement("div", { key: contributor.id, className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, contributor.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: contributor.avatar_url, alt: contributor.name || contributor.username, className: "h-11 w-11 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "truncate font-semibold text-white" }, contributor.name || contributor.username), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, contributor.role_label || "Contributor")))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No contributor credits yet."))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "Milestones"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, milestones.length > 0 ? milestones.map((milestone) => /* @__PURE__ */ React.createElement("div", { key: milestone.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, milestone.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, milestone.status)), milestone.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, milestone.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs text-slate-500" }, milestone.owner?.name || milestone.owner?.username || "No owner", milestone.due_date ? ` • due ${milestone.due_date}` : ""))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No milestones defined yet."))))))); } -const __vite_glob_0_63 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_67 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupReleaseShow }, Symbol.toStringTag, { value: "Module" })); @@ -78059,7 +82006,7 @@ function csrfToken$2() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || ""; } function GroupShow() { - const { props } = X(); + const { props } = X$1(); const group = props.group || {}; const section = props.section || "overview"; const featuredArtworks = Array.isArray(props.featuredArtworks) ? props.featuredArtworks : []; @@ -78213,7 +82160,7 @@ function GroupShow() { return /* @__PURE__ */ React.createElement("section", { key: label, className: "relative overflow-hidden rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: `absolute inset-x-0 top-0 h-[3px] rounded-t-[30px] bg-gradient-to-r ${roleKey === "owner" ? "from-amber-400/70 to-transparent" : roleKey === "admin" ? "from-sky-400/70 to-transparent" : roleKey === "editor" ? "from-violet-400/70 to-transparent" : "from-emerald-400/70 to-transparent"}` }), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: `inline-flex h-8 w-8 items-center justify-center rounded-xl border ${roleStyle.badge}` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${roleStyle.icon} fa-fw text-sm ${roleStyle.iconColor}` })), /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, label), /* @__PURE__ */ React.createElement("span", { className: "ml-auto rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300" }, bucket.length)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3" }, bucket.map((member) => /* @__PURE__ */ React.createElement("a", { key: member.id, href: member.user?.profile_url || "#", className: "group flex items-center gap-3 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4 transition hover:border-white/20 hover:bg-white/[0.04]" }, member.user?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: member.user.avatar_url, alt: member.user.name || member.user.username, className: "h-12 w-12 shrink-0 rounded-2xl object-cover ring-1 ring-white/10 transition group-hover:ring-white/20" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "truncate font-semibold text-white" }, member.user?.name || member.user?.username), /* @__PURE__ */ React.createElement("div", { className: `mt-1 inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] ${roleStyle.badge}` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${roleStyle.icon} fa-fw text-[9px]` }), member.role_label || member.role)))))); }))) : null, section === "about" ? /* @__PURE__ */ React.createElement("section", { className: "mt-8 relative overflow-hidden rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-x-0 top-0 h-[3px] rounded-t-[30px] bg-gradient-to-r from-slate-400/50 to-transparent" }), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-10 w-10 items-center justify-center rounded-[14px] border border-white/10 bg-white/[0.05] text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-id-card fa-fw" })), /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold text-white" }, "About")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-4 text-sm leading-7 text-slate-300" }, /* @__PURE__ */ React.createElement("p", null, group.bio || "No long-form description yet."), group.website_url ? /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("a", { href: group.website_url, className: "inline-flex items-center gap-1.5 text-sky-200 underline underline-offset-4 transition hover:text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-link" }), group.website_url)) : null, Array.isArray(group.links) && group.links.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, group.links.map((link2) => /* @__PURE__ */ React.createElement("a", { key: `${link2.label}-${link2.url}`, href: link2.url, className: "inline-flex items-center gap-2 rounded-[14px] border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-up-right-from-square text-slate-400" }), link2.label))) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 border-t border-white/8 pt-4 text-xs text-slate-400" }, group.founded_at ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-calendar-days text-slate-500" }), "Founded ", new Date(group.founded_at).toLocaleDateString()) : null, group.type ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-tag text-slate-500" }), group.type) : null))) : null)); } -const __vite_glob_0_64 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_68 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: GroupShow }, Symbol.toStringTag, { value: "Module" })); @@ -78372,7 +82319,7 @@ function BulletGrid$7({ items, tone = "sky" }) { return /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-3 text-sm leading-6 text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: `mt-2 h-2 w-2 shrink-0 rounded-full ${dotColor}` }), /* @__PURE__ */ React.createElement("span", null, item))))); } function AccountHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const signedIn = Boolean(props.auth?.signed_in); const jsonLd = [ @@ -78471,7 +82418,7 @@ function AccountHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-emerald-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: signedIn ? links.profile_settings : links.login, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, signedIn ? "Open account settings" : "Open login"), /* @__PURE__ */ React.createElement("a", { href: links.help_auth, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read auth help"), /* @__PURE__ */ React.createElement("a", { href: links.help_profile, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Profile help"), /* @__PURE__ */ React.createElement("a", { href: links.help_troubleshooting, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open troubleshooting"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-emerald-300/20 bg-emerald-400/10 p-4 text-emerald-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-emerald-50/85" }, "The healthiest account is the one with a current email, a manageable password, and settings reviewed before they become emergency work."))))))); } -const __vite_glob_0_65 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_69 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AccountHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -78709,7 +82656,7 @@ function TroubleCard$5({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function AuthHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const signedIn = Boolean(props.auth?.signed_in); const jsonLd = [ @@ -78827,7 +82774,7 @@ function AuthHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.login, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open login"), /* @__PURE__ */ React.createElement("a", { href: links.register, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Create account"), /* @__PURE__ */ React.createElement("a", { href: links.password_request, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Reset password"), /* @__PURE__ */ React.createElement("a", { href: links.help_account, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read account settings help"), /* @__PURE__ */ React.createElement("a", { href: links.help_troubleshooting, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open troubleshooting hub"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "If access breaks, check four things first: the email, the password, the inbox, and whether the problem is really permissions rather than login."))))))); } -const __vite_glob_0_66 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_70 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: AuthHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -79124,7 +83071,7 @@ function TroubleCard$4({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function CardsHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const jsonLd = [ { @@ -79253,7 +83200,7 @@ function CardsHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.create_card, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Create a Card"), /* @__PURE__ */ React.createElement("a", { href: links.studio_cards, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Cards workspace"), /* @__PURE__ */ React.createElement("a", { href: links.cards_index, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Browse public Cards"), /* @__PURE__ */ React.createElement("a", { href: links.studio_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Studio help"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "If the content feels unclear, ask one question first: is this a Card, an artwork, a post, or a collection? The answer usually fixes the workflow too."))))))); } -const __vite_glob_0_67 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_71 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: CardsHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -80029,7 +83976,7 @@ function PopularTopicCard({ item, href }) { return /* @__PURE__ */ React.createElement("a", { href, className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-base font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-300" }, item.description)); } function HelpCenterPage() { - const page = X(); + const page = X$1(); const { props, url } = page; const links = props.links || {}; const signedIn = Boolean(props.auth?.signed_in); @@ -80162,7 +84109,7 @@ function HelpCenterPage() { /* @__PURE__ */ React.createElement(HelpSupportCta, { items: supportItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Help architecture"), /* @__PURE__ */ React.createElement("ul", { className: "mt-4 space-y-3 text-sm leading-6 text-slate-300" }, /* @__PURE__ */ React.createElement("li", null, "Use ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, "/help"), " as the main hub."), /* @__PURE__ */ React.createElement("li", null, "Use ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, "/help/topic"), " for overview pages."), /* @__PURE__ */ React.createElement("li", null, "Use ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, "/help/topic/subpage"), " for quickstarts, FAQs, and troubleshooting."))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Current coverage"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-300" }, "Groups is the first complete multi-page topic family, and Studio, Upload, Cards, Profile, Signup / Login, Account Settings, and Troubleshooting are now live topic guides. The rest of the Help Center still follows the same predictable expansion path."))))))); } -const __vite_glob_0_68 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_72 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: HelpCenterPage }, Symbol.toStringTag, { value: "Module" })); @@ -80423,7 +84370,7 @@ function TroubleCard$3({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function ProfileHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const jsonLd = [ { @@ -80551,7 +84498,7 @@ function ProfileHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.profile_settings, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open profile settings"), /* @__PURE__ */ React.createElement("a", { href: links.groups_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Groups help"), /* @__PURE__ */ React.createElement("a", { href: links.studio_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Studio help"), /* @__PURE__ */ React.createElement("a", { href: links.upload_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Upload help"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "A better profile usually starts with three things: a recognizable avatar, a clearer bio, and a stronger sense of what you want people to remember about you."))))))); } -const __vite_glob_0_69 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_73 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ProfileHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -80853,7 +84800,7 @@ function TroubleCard$2({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function StudioHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const jsonLd = [ { @@ -81006,7 +84953,7 @@ function StudioHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.open_studio, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Studio"), /* @__PURE__ */ React.createElement("a", { href: links.studio_drafts, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open drafts"), /* @__PURE__ */ React.createElement("a", { href: links.group_studio, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Group Studio"), /* @__PURE__ */ React.createElement("a", { href: links.groups_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Groups help"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "If something feels missing, check context first. Personal Studio and Group Studio are connected, but they are not identical workspaces."))))))); } -const __vite_glob_0_70 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_74 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -81149,7 +85096,7 @@ function BulletGrid$2({ items, tone = "sky" }) { return /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-3 text-sm leading-6 text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: `mt-2 h-2 w-2 shrink-0 rounded-full ${dotColor}` }), /* @__PURE__ */ React.createElement("span", null, item))))); } function TroubleshootingHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const signedIn = Boolean(props.auth?.signed_in); const jsonLd = [ @@ -81248,7 +85195,7 @@ function TroubleshootingHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-rose-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.help_auth, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read auth help"), /* @__PURE__ */ React.createElement("a", { href: links.help_account, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read account settings help"), /* @__PURE__ */ React.createElement("a", { href: links.upload_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Upload help"), /* @__PURE__ */ React.createElement("a", { href: links.groups_faq, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Groups FAQ"), /* @__PURE__ */ React.createElement("a", { href: links.report_issue, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Report a problem"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-rose-300/20 bg-rose-400/10 p-4 text-rose-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-rose-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-rose-50/85" }, "A clear problem statement beats frantic guessing. Name the route, the context, and what changed before you decide the product is broken."))))))); } -const __vite_glob_0_71 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_75 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: TroubleshootingHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -81514,7 +85461,7 @@ function TroubleCard$1({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function UploadHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const jsonLd = [ { @@ -81666,7 +85613,7 @@ function UploadHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.upload, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Start upload"), /* @__PURE__ */ React.createElement("a", { href: links.studio_drafts, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open drafts"), /* @__PURE__ */ React.createElement("a", { href: links.studio_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Studio help"), /* @__PURE__ */ React.createElement("a", { href: links.groups_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Groups help"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-amber-300/20 bg-amber-400/10 p-4 text-amber-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-amber-50/85" }, "If an upload feels wrong, check three things first: context, draft state, and contributor credit."))))))); } -const __vite_glob_0_72 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_76 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: UploadHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -81978,7 +85925,7 @@ function TroubleCard({ item, links }) { return /* @__PURE__ */ React.createElement("a", { href: links[item.linkKey], className: "rounded-[28px] border border-white/10 bg-black/20 p-5 transition hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-sky-200" }, item.linkLabel), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "→"))); } function WorldsHelpPage() { - const { props } = X(); + const { props } = X$1(); const links = props.links || {}; const jsonLd = [ { @@ -82129,7 +86076,7 @@ function WorldsHelpPage() { /* @__PURE__ */ React.createElement(QuickstartNextSteps, { items: relatedHelpItems }) )), /* @__PURE__ */ React.createElement("aside", { className: "hidden xl:block xl:sticky xl:top-24 xl:self-start" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4 rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(2,6,23,0.22)]" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Quick route map"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React.createElement("a", { href: links.create_world, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Create a World"), /* @__PURE__ */ React.createElement("a", { href: links.studio_worlds, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Open Worlds workspace"), /* @__PURE__ */ React.createElement("a", { href: links.worlds_index, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Browse public Worlds"), /* @__PURE__ */ React.createElement("a", { href: links.studio_help, className: "block rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.05]" }, "Read Studio help"))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-sky-300/20 bg-sky-400/10 p-4 text-sky-50" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-100/80" }, "Fast reminder"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-sky-50/85" }, "A World should feel like an editorial decision, not a container. If the page feels cluttered, the usual fix is stronger curation, fewer modules, and clearer promotion intent."))))))); } -const __vite_glob_0_73 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_77 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: WorldsHelpPage }, Symbol.toStringTag, { value: "Module" })); @@ -82201,7 +86148,7 @@ const API_BY_TYPE = { world: "/api/leaderboard/worlds" }; function LeaderboardPage() { - const { props } = X(); + const { props } = X$1(); const { initialType = "creator", initialPeriod = "weekly", initialData = { items: [] }, seo = {} } = props; const [type2, setType] = reactExports.useState(initialType); const [period, setPeriod] = reactExports.useState(initialPeriod); @@ -82240,7 +86187,7 @@ function LeaderboardPage() { const items = Array.isArray(data?.items) ? data.items : []; return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SeoHead, { seo, title: seo?.title || "Leaderboard — Skinbase", description: seo?.description || "Top creators, groups, artworks, stories, and Worlds on Skinbase." }), /* @__PURE__ */ React.createElement("div", { className: "min-h-screen bg-[radial-gradient(circle_at_top,rgba(14,165,233,0.14),transparent_34%),linear-gradient(180deg,#020617_0%,#0f172a_48%,#020617_100%)] pb-16 text-slate-100" }, /* @__PURE__ */ React.createElement("div", { className: "mx-auto w-full max-w-7xl px-4 py-8 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement("header", { className: "rounded-[2rem] border border-white/10 bg-slate-950/70 px-6 py-8 shadow-[0_35px_120px_rgba(2,6,23,0.75)] backdrop-blur" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold uppercase tracking-[0.28em] text-sky-300" }, "Skinbase Competition Board"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 max-w-3xl text-4xl font-black tracking-tight text-white sm:text-5xl" }, "Top creators, groups, standout artworks, stories, and Worlds with momentum."), /* @__PURE__ */ React.createElement("p", { className: "mt-4 max-w-2xl text-sm leading-6 text-slate-300 sm:text-base" }, "Switch between creators, groups, artworks, stories, and Worlds, then filter by daily, weekly, monthly, or all-time performance.")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-4" }, /* @__PURE__ */ React.createElement(LeaderboardTabs, { items: TYPE_TABS, active: type2, onChange: setType, sticky: true, label: "Leaderboard type" }), /* @__PURE__ */ React.createElement(LeaderboardTabs, { items: PERIOD_TABS, active: period, onChange: setPeriod, label: "Leaderboard period" })), loading ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-3xl border border-white/10 bg-white/[0.03] px-6 py-5 text-sm text-slate-400" }, "Refreshing leaderboard...") : null, /* @__PURE__ */ React.createElement("div", { className: "mt-8" }, /* @__PURE__ */ React.createElement(LeaderboardList, { items, type: type2 }))))); } -const __vite_glob_0_74 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_78 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: LeaderboardPage }, Symbol.toStringTag, { value: "Module" })); @@ -82275,7 +86222,7 @@ function getEcho() { const resolvedHost = useWindowHost ? publicHostname : configuredHost; const resolvedPort = useWindowHost ? Number(443) : Number("443"); const resolvedSecurePort = useWindowHost ? Number(443) : Number("443"); - echoInstance = new E$1({ + echoInstance = new E$2({ broadcaster: "reverb", key, wsHost: resolvedHost, @@ -84011,12 +87958,12 @@ function MessagesPage({ userId, username, activeConversationId: initialId }) { const activeConversation = conversations.find((conversation) => conversation.id === activeId) ?? null; const unreadCount = Number.isFinite(Number(unreadTotal)) ? Number(unreadTotal) : conversations.reduce((sum, conversation) => sum + Number(conversation.unread_count || 0), 0); const pinnedCount = conversations.reduce((sum, conversation) => { - const me = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId); - return sum + (me?.is_pinned ? 1 : 0); + const me2 = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId); + return sum + (me2?.is_pinned ? 1 : 0); }, 0); const archivedCount = conversations.reduce((sum, conversation) => { - const me = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId); - return sum + (me?.is_archived ? 1 : 0); + const me2 = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId); + return sum + (me2?.is_archived ? 1 : 0); }, 0); const activeSearch = searchQuery.trim().length >= 2; const activeConversationLabel = activeConversation?.title || activeConversation?.all_participants?.find((participant) => participant.user_id !== userId)?.user?.username || "Conversation"; @@ -84178,7 +88125,7 @@ if (typeof document !== "undefined") { ); } } -const __vite_glob_0_75 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_79 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: MessagesPage }, Symbol.toStringTag, { value: "Module" })); @@ -84212,7 +88159,7 @@ function Badge({ children, tone = "slate" }) { return /* @__PURE__ */ React.createElement("span", { className: `inline-flex items-center rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] ${tones2[tone] || tones2.slate}` }, children); } function ArtworkMaturityQueue() { - const { props } = X(); + const { props } = X$1(); const [items, setItems] = reactExports.useState(props.initialItems || []); const [stats, setStats] = reactExports.useState(props.stats || {}); const [status2, setStatus] = reactExports.useState(props.initialFilters?.status || "suspected"); @@ -84274,7 +88221,7 @@ function ArtworkMaturityQueue() { { key: "reviewed", label: "Reviewed", value: Number(stats.reviewed || 0) }, { key: "mature", label: "Marked mature", value: Number(stats.mature || 0) } ], [stats]); - return /* @__PURE__ */ React.createElement("div", { className: "w-full pb-16 pt-8" }, /* @__PURE__ */ React.createElement(Se, { title: "Artwork Maturity Queue" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(251,191,36,0.16),transparent_36%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-amber-200/80" }, "Moderator surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Artwork maturity review"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-relaxed text-slate-300" }, "Review uploads where the uploader declaration and AI suspicion do not match, plus legacy artworks detected by the non-mutating thumbnail audit. Audit candidates stay read-only until a moderator confirms the final maturity state.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, statusSummary.map((entry) => (() => { + return /* @__PURE__ */ React.createElement("div", { className: "w-full pb-16 pt-8" }, /* @__PURE__ */ React.createElement(Se$1, { title: "Artwork Maturity Queue" }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(251,191,36,0.16),transparent_36%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-amber-200/80" }, "Moderator surface"), /* @__PURE__ */ React.createElement("h1", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Artwork maturity review"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-relaxed text-slate-300" }, "Review uploads where the uploader declaration and AI suspicion do not match, plus legacy artworks detected by the non-mutating thumbnail audit. Audit candidates stay read-only until a moderator confirms the final maturity state.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, statusSummary.map((entry) => (() => { const queueKey = queueStatusKey(entry.key); return /* @__PURE__ */ React.createElement( "button", @@ -84345,7 +88292,7 @@ function ArtworkMaturityQueue() { } )); } -const __vite_glob_0_77 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_81 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ArtworkMaturityQueue }, Symbol.toStringTag, { value: "Module" })); @@ -84766,10 +88713,97 @@ if (typeof document !== "undefined") { clientExports.createRoot(mountEl).render(/* @__PURE__ */ React.createElement(NewsComments, { ...props })); } } -const __vite_glob_0_78 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_82 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: NewsComments }, Symbol.toStringTag, { value: "Module" })); +let previewOverlay = null; +let previewImage = null; +let previewCaption = null; +let scrollPosition = 0; +function ensurePreviewOverlay() { + if (previewOverlay) { + return previewOverlay; + } + previewOverlay = document.createElement("div"); + previewOverlay.className = "fixed inset-0 z-[130] hidden items-center justify-center bg-[#020611e8] p-4 backdrop-blur-md"; + previewOverlay.setAttribute("role", "dialog"); + previewOverlay.setAttribute("aria-modal", "true"); + previewOverlay.setAttribute("aria-label", "Image preview"); + const frame = document.createElement("div"); + frame.className = "relative max-h-[92vh] max-w-6xl"; + previewImage = document.createElement("img"); + previewImage.className = "max-h-[92vh] max-w-full rounded-[28px] border border-white/10 shadow-[0_28px_90px_rgba(2,6,23,0.6)]"; + previewImage.alt = "Image preview"; + previewCaption = document.createElement("div"); + previewCaption.className = "absolute inset-x-0 bottom-0 rounded-b-[28px] bg-gradient-to-t from-black/80 to-transparent px-5 py-4 text-sm font-medium text-white/90"; + const closeButton = document.createElement("button"); + closeButton.type = "button"; + closeButton.setAttribute("aria-label", "Close image preview"); + closeButton.className = "absolute right-4 top-4 inline-flex h-11 w-11 items-center justify-center rounded-full border border-white/10 bg-black/35 text-white transition hover:border-white/25 hover:bg-white/10"; + closeButton.innerHTML = ''; + closeButton.addEventListener("click", hidePreview); + frame.appendChild(previewImage); + frame.appendChild(previewCaption); + frame.appendChild(closeButton); + previewOverlay.appendChild(frame); + previewOverlay.addEventListener("click", (event) => { + if (event.target === previewOverlay) { + hidePreview(); + } + }); + document.body.appendChild(previewOverlay); + return previewOverlay; +} +function showPreview(src2, alt) { + if (!src2) return; + ensurePreviewOverlay(); + scrollPosition = window.scrollY || document.documentElement.scrollTop || 0; + document.body.style.position = "fixed"; + document.body.style.top = `-${scrollPosition}px`; + document.body.style.left = "0"; + document.body.style.right = "0"; + document.body.style.width = "100%"; + previewImage.src = src2; + previewImage.alt = alt; + previewCaption.textContent = alt; + previewOverlay.classList.remove("hidden"); + previewOverlay.classList.add("flex"); +} +function hidePreview() { + if (!previewOverlay) return; + previewOverlay.classList.add("hidden"); + previewOverlay.classList.remove("flex"); + previewImage.removeAttribute("src"); + document.body.style.position = ""; + document.body.style.top = ""; + document.body.style.left = ""; + document.body.style.right = ""; + document.body.style.width = ""; + window.scrollTo(0, scrollPosition); +} +function handleNewsImagePreview(event) { + const trigger = event.target?.closest?.("[data-news-image-preview]"); + if (!trigger) return; + const src2 = trigger.getAttribute("data-news-image-src") || trigger.getAttribute("href"); + if (!src2) return; + event.preventDefault(); + showPreview(src2, trigger.getAttribute("data-news-image-alt") || trigger.getAttribute("aria-label") || "Image preview"); +} +function handleKeyDown(event) { + if (event.key === "Escape") { + hidePreview(); + } +} +if (typeof document !== "undefined") { + document.addEventListener("click", handleNewsImagePreview); + document.addEventListener("keydown", handleKeyDown); +} +const NewsImagePreview = null; +const __vite_glob_0_83 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: NewsImagePreview +}, Symbol.toStringTag, { value: "Module" })); function clamp(value, min2, max2) { return Math.min(max2, Math.max(min2, value)); } @@ -85026,7 +89060,7 @@ function formatCompactNumber$1(value) { return numeric.toLocaleString("en-US"); } function ProfileHero({ user, profile, isOwner, viewerIsFollowing, followerCount, recentFollowers = [], followContext = null, heroBgUrl, countryName, leaderboardRank, extraActions = null }) { - const { props } = X(); + const { props } = X$1(); const [following, setFollowing] = reactExports.useState(viewerIsFollowing); const [count, setCount] = reactExports.useState(followerCount); const [editorOpen, setEditorOpen] = reactExports.useState(false); @@ -85676,7 +89710,7 @@ function ProfileGalleryPanel({ artworks, username }) { ))); } function ProfileGallery() { - const { props } = X(); + const { props } = X$1(); const { user, profile, @@ -85730,7 +89764,7 @@ function ProfileGallery() { } )))); } -const __vite_glob_0_79 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_84 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ProfileGallery }, Symbol.toStringTag, { value: "Module" })); @@ -86446,7 +90480,7 @@ function SectionCard$2({ icon, eyebrow, title, children, className = "" }) { return /* @__PURE__ */ React.createElement("section", { className: `rounded-[28px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_52px_rgba(2,6,23,0.18)] md:p-6 ${className}`.trim() }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.05] text-sky-300" }, /* @__PURE__ */ React.createElement("i", { className: `${icon} text-base` })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, eyebrow), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 text-xl font-semibold tracking-[-0.02em] text-white md:text-2xl" }, title))), /* @__PURE__ */ React.createElement("div", { className: "mt-5" }, children)); } function TabAbout({ user, profile, stats, achievements, worldRewards, artworks, creatorStories, profileComments, socialLinks, countryName, followerCount, recentFollowers, leaderboardRank, groupContributionHistory, journey }) { - const { props } = X(); + const { props } = X$1(); const uname = user.username || user.name; const displayName = user.name || uname; const about = profile?.about; @@ -87311,15 +91345,15 @@ function ShareArtworkModal({ isOpen, onClose, onShared, preselectedArtwork = nul placeholder: "Say something about this artwork…", className: "w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2.5 text-sm text-white resize-none placeholder-slate-600 focus:outline-none focus:border-sky-500/50 transition-colors" } - ), /* @__PURE__ */ React.createElement("p", { className: "text-right text-[10px] text-slate-600 mt-0.5" }, body2.length, "/2000")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: "block text-xs font-medium text-slate-400 mb-1.5" }, "Visibility"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, VISIBILITY_OPTIONS$1.map((v) => /* @__PURE__ */ React.createElement( + ), /* @__PURE__ */ React.createElement("p", { className: "text-right text-[10px] text-slate-600 mt-0.5" }, body2.length, "/2000")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: "block text-xs font-medium text-slate-400 mb-1.5" }, "Visibility"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, VISIBILITY_OPTIONS$1.map((v2) => /* @__PURE__ */ React.createElement( "button", { - key: v.value, - onClick: () => setVisibility(v.value), - className: `flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium transition-all border ${visibility === v.value ? "border-sky-500/50 bg-sky-500/10 text-sky-300" : "border-white/10 bg-white/[0.03] text-slate-400 hover:border-white/20 hover:text-white"}` + key: v2.value, + onClick: () => setVisibility(v2.value), + className: `flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium transition-all border ${visibility === v2.value ? "border-sky-500/50 bg-sky-500/10 text-sky-300" : "border-white/10 bg-white/[0.03] text-slate-400 hover:border-white/20 hover:text-white"}` }, - /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v.icon} fa-fw` }), - v.label + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v2.icon} fa-fw` }), + v2.label )))), error && /* @__PURE__ */ React.createElement("p", { className: "text-xs text-rose-400 bg-rose-500/10 border border-rose-500/20 rounded-xl px-3 py-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-circle-exclamation mr-1.5" }), error)), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2 px-5 py-4 border-t border-white/[0.06]" }, /* @__PURE__ */ React.createElement( "button", { @@ -87484,8 +91518,8 @@ const VISIBILITY_OPTIONS = [ ]; const URL_RE = /https?:\/\/[^\s\])"'>]{4,}/gi; function extractFirstUrl(text2) { - const m = text2.match(URL_RE); - return m ? m[0].replace(/[.,;:!?)]+$/, "") : null; + const m2 = text2.match(URL_RE); + return m2 ? m2[0].replace(/[.,;:!?)]+$/, "") : null; } function PostComposer({ user, onPosted }) { const [expanded, setExpanded] = reactExports.useState(false); @@ -87512,7 +91546,7 @@ function PostComposer({ user, onPosted }) { const { default: data } = await import("./assets/emoji-data-4xGXbtDn.js"); setEmojiData(data); } - setEmojiOpen((v) => !v); + setEmojiOpen((v2) => !v2); }, [emojiData]); reactExports.useEffect(() => { if (!emojiOpen) return; @@ -87614,7 +91648,7 @@ function PostComposer({ user, onPosted }) { onPosted?.(newPost); setShareModal(false); }; - const showPreview = (linkPreview || previewLoading) && !previewDismissed; + const showPreview2 = (linkPreview || previewLoading) && !previewDismissed; return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/[0.08] bg-white/[0.025] px-5 py-4" }, !expanded ? /* @__PURE__ */ React.createElement( "div", { @@ -87667,11 +91701,11 @@ function PostComposer({ user, onPosted }) { "button", { type: "button", - onClick: () => setTaggedUsers((prev) => prev.filter((x) => x.id !== u2.id)), + onClick: () => setTaggedUsers((prev) => prev.filter((x2) => x2.id !== u2.id)), className: "opacity-60 hover:opacity-100 ml-0.5" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-xmark fa-xs" }) - )))), showPreview && /* @__PURE__ */ React.createElement("div", { className: "pl-12" }, /* @__PURE__ */ React.createElement( + )))), showPreview2 && /* @__PURE__ */ React.createElement("div", { className: "pl-12" }, /* @__PURE__ */ React.createElement( LinkPreviewCard, { preview: linkPreview, @@ -87697,17 +91731,17 @@ function PostComposer({ user, onPosted }) { title: "Clear" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-xmark fa-sm" }) - ))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 pl-12" }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-1" }, VISIBILITY_OPTIONS.map((v) => /* @__PURE__ */ React.createElement( + ))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 pl-12" }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-1" }, VISIBILITY_OPTIONS.map((v2) => /* @__PURE__ */ React.createElement( "button", { - key: v.value, + key: v2.value, type: "button", - onClick: () => setVisibility(v.value), - title: v.label, - className: `flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs transition-all ${visibility === v.value ? "bg-sky-500/15 text-sky-400 border border-sky-500/30" : "text-slate-500 hover:text-white hover:bg-white/5"}` + onClick: () => setVisibility(v2.value), + title: v2.label, + className: `flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs transition-all ${visibility === v2.value ? "bg-sky-500/15 text-sky-400 border border-sky-500/30" : "text-slate-500 hover:text-white hover:bg-white/5"}` }, - /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v.icon} fa-fw` }), - visibility === v.value && /* @__PURE__ */ React.createElement("span", null, v.label) + /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${v2.icon} fa-fw` }), + visibility === v2.value && /* @__PURE__ */ React.createElement("span", null, v2.label) ))), /* @__PURE__ */ React.createElement("div", { ref: emojiWrapRef, className: "relative" }, /* @__PURE__ */ React.createElement( "button", { @@ -87744,7 +91778,7 @@ function PostComposer({ user, onPosted }) { "button", { type: "button", - onClick: () => setScheduleOpen((v) => !v), + onClick: () => setScheduleOpen((v2) => !v2), title: "Schedule post", className: `flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs transition-all ${scheduleOpen || scheduledAt ? "bg-violet-500/15 text-violet-400 border border-violet-500/30" : "text-slate-500 hover:text-white hover:bg-white/5"}` }, @@ -88377,7 +92411,7 @@ function getInitialTab(initialTab = "posts") { return VALID_TABS.includes(initialTab) ? initialTab : "posts"; } function ProfileShow() { - const { props } = X(); + const { props } = X$1(); const { user, profile, @@ -88441,8 +92475,8 @@ function ProfileShow() { const artworkNextCursor = artworks?.next_cursor ?? null; const favouriteList = Array.isArray(favourites) ? favourites : favourites?.data ?? []; const favouriteNextCursor = favourites?.next_cursor ?? null; - const socialLinksObj = Array.isArray(socialLinks) ? socialLinks.reduce((acc, l) => { - acc[l.platform] = l; + const socialLinksObj = Array.isArray(socialLinks) ? socialLinks.reduce((acc, l3) => { + acc[l3.platform] = l3; return acc; }, {}) : socialLinks ?? {}; const contentShellClassName = activeTab === "artworks" ? "w-full px-4 md:px-6" : activeTab === "posts" || activeTab === "about" ? "mx-auto max-w-7xl px-4 md:px-6" : "max-w-6xl mx-auto px-4"; @@ -88580,7 +92614,7 @@ function ProfileShow() { } )))); } -const __vite_glob_0_80 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_85 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ProfileShow }, Symbol.toStringTag, { value: "Module" })); @@ -88631,7 +92665,7 @@ function SectionSidebar({ sections = [], activeSection, onSectionChange, dirtyMa }))); } function SettingsLayout({ children, title, sections = null, activeSection = null, onSectionChange = null, dirtyMap = {} }) { - const { url } = X(); + const { url } = X$1(); const [mobileOpen, setMobileOpen] = reactExports.useState(false); const hasSectionMode = Array.isArray(sections) && sections.length > 0 && typeof onSectionChange === "function"; const isActive = (href) => url.startsWith(href); @@ -88833,7 +92867,7 @@ function SectionCard$1({ title, description, icon, children, actionSlot }) { return /* @__PURE__ */ React.createElement("section", { className: "rounded-2xl border border-white/[0.06] bg-gradient-to-b from-white/[0.04] to-white/[0.02] p-6 shadow-lg shadow-black/10" }, /* @__PURE__ */ React.createElement("header", { className: "flex flex-col gap-4 border-b border-white/[0.06] px-1.5 pb-4 md:flex-row md:items-center md:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, icon ? /* @__PURE__ */ React.createElement("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center self-start rounded-xl bg-accent/10 text-accent md:self-center" }, /* @__PURE__ */ React.createElement("i", { className: `${icon} text-sm` })) : null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-base font-semibold text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, description) : null)), actionSlot ? /* @__PURE__ */ React.createElement("div", { className: "shrink-0 self-start md:self-center" }, actionSlot) : null), /* @__PURE__ */ React.createElement("div", { className: "px-1.5 pt-5" }, children)); } function ProfileEdit() { - const { props } = X(); + const { props } = X$1(); const { user, avatarUrl: initialAvatarUrl, @@ -90008,7 +94042,7 @@ function ProfileEdit() { ) ); } -const __vite_glob_0_81 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_86 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: ProfileEdit }, Symbol.toStringTag, { value: "Module" })); @@ -90235,7 +94269,7 @@ function buildStudioContextOptions(currentGroup, studioGroups, userLabel, userAv ]; } function StudioLayout({ children, title, subtitle, actions }) { - const { url, props } = X(); + const { url, props } = X$1(); const [mobileOpen, setMobileOpen] = reactExports.useState(false); const [createOpen, setCreateOpen] = reactExports.useState(false); const pathname = url.split("?")[0]; @@ -90476,7 +94510,7 @@ function formatDate$7(value) { return date.toLocaleString(void 0, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }); } function StudioActivity() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const filters = listing.filters || {}; const items = listing.items || []; @@ -90521,7 +94555,7 @@ function StudioActivity() { /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "New since last read"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.new_items || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Unread notifications"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.unread_notifications || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Last inbox reset"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-base font-semibold text-white" }, summary.last_read_at ? formatDate$7(summary.last_read_at) : "Not yet"))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_35%),linear-gradient(135deg,_rgba(15,23,42,0.86),_rgba(2,6,23,0.96))] p-5 lg:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search activity"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilters({ q: event.target.value }), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white", placeholder: "Message, actor, or module" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.type || "all", onChange: (val) => updateFilters({ type: val }), options: typeOptions, searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Content type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.module || "all", onChange: (val) => updateFilters({ module: val }), options: moduleOptions, searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateFilters({ q: "", type: "all", module: "all" }), className: "w-full rounded-2xl border border-white/10 px-4 py-3 text-sm text-slate-200" }, "Reset")))), /* @__PURE__ */ React.createElement("section", { className: "space-y-4" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: `rounded-[28px] border p-5 ${item.is_new ? "border-sky-300/25 bg-sky-300/10" : "border-white/10 bg-white/[0.03]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-4" }, item.actor?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: item.actor.avatar_url, alt: item.actor.name || "Activity actor", className: "h-12 w-12 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-black/20 text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-bell" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, item.module_label), /* @__PURE__ */ React.createElement("span", null, formatDate$7(item.created_at)), item.is_new && /* @__PURE__ */ React.createElement("span", { className: "rounded-full bg-sky-300/20 px-2 py-1 text-sky-100" }, "New")), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap items-center gap-3 text-sm text-slate-400" }, item.actor?.name && /* @__PURE__ */ React.createElement("span", null, item.actor.name), /* @__PURE__ */ React.createElement("a", { href: item.url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-slate-200" }, "Open")))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/15 px-6 py-16 text-center text-slate-400" }, "No activity matches this filter.")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) <= 1, onClick: () => updateFilters({ page: Math.max(1, (meta.current_page || 1) - 1) }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Previous"), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, "Page ", meta.current_page || 1, " of ", meta.last_page || 1), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) >= (meta.last_page || 1), onClick: () => updateFilters({ page: (meta.current_page || 1) + 1 }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Next"))) ); } -const __vite_glob_0_82 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_87 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioActivity }, Symbol.toStringTag, { value: "Module" })); @@ -90549,7 +94583,7 @@ function TrendChart({ title, subtitle, points, colorClass, fillClass, icon }) { }))); } function StudioAnalytics() { - const { props } = X(); + const { props } = X$1(); const { totals, topContent, @@ -90612,7 +94646,7 @@ function StudioAnalytics() { return /* @__PURE__ */ React.createElement("div", { key: item.key, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-200" }, /* @__PURE__ */ React.createElement("i", { className: item.icon }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, item.label), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-400" }, Number(item.published_count || 0).toLocaleString(), " published"))), /* @__PURE__ */ React.createElement("a", { href: moduleBreakdown?.find((entry) => entry.key === item.key)?.index_url, className: "text-xs font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Open")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, "Views"), /* @__PURE__ */ React.createElement("span", null, Number(item.views || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "mt-2 h-2 overflow-hidden rounded-full bg-white/5" }, /* @__PURE__ */ React.createElement("div", { className: "h-full rounded-full bg-emerald-400/60", style: { width: `${Math.max(4, Math.round(Number(item.views || 0) / viewMax * 100))}%` } }))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, "Engagement"), /* @__PURE__ */ React.createElement("span", null, Number(item.engagement || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "mt-2 h-2 overflow-hidden rounded-full bg-white/5" }, /* @__PURE__ */ React.createElement("div", { className: "h-full rounded-full bg-pink-400/60", style: { width: `${Math.max(4, Math.round(Number(item.engagement || 0) / engagementMax * 100))}%` } }))))); }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Readable insights"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3 text-sm text-slate-400" }, (insightBlocks || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.key, href: item.href, className: "block rounded-[22px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded-2xl bg-white/[0.04] text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: item.icon })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 leading-6 text-slate-400" }, item.body), /* @__PURE__ */ React.createElement("span", { className: "mt-3 inline-flex items-center gap-2 text-sm font-medium text-sky-100" }, item.cta, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }))))))))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Top content"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 overflow-x-auto" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-b border-white/5 text-left text-[11px] uppercase tracking-[0.18em] text-slate-500" }, /* @__PURE__ */ React.createElement("th", { className: "pb-3 pr-4" }, "Module"), /* @__PURE__ */ React.createElement("th", { className: "pb-3 pr-4" }, "Title"), /* @__PURE__ */ React.createElement("th", { className: "pb-3 pr-4 text-right" }, "Views"), /* @__PURE__ */ React.createElement("th", { className: "pb-3 pr-4 text-right" }, "Reactions"), /* @__PURE__ */ React.createElement("th", { className: "pb-3 pr-4 text-right" }, "Comments"), /* @__PURE__ */ React.createElement("th", { className: "pb-3 text-right" }, "Open"))), /* @__PURE__ */ React.createElement("tbody", { className: "divide-y divide-white/5" }, (topContent || []).map((item) => /* @__PURE__ */ React.createElement("tr", { key: item.id }, /* @__PURE__ */ React.createElement("td", { className: "py-3 pr-4 text-slate-300" }, item.module_label), /* @__PURE__ */ React.createElement("td", { className: "py-3 pr-4 text-white" }, item.title), /* @__PURE__ */ React.createElement("td", { className: "py-3 pr-4 text-right text-slate-300" }, Number(item.metrics?.views || 0).toLocaleString()), /* @__PURE__ */ React.createElement("td", { className: "py-3 pr-4 text-right text-slate-300" }, Number(item.metrics?.appreciation || 0).toLocaleString()), /* @__PURE__ */ React.createElement("td", { className: "py-3 pr-4 text-right text-slate-300" }, Number(item.metrics?.comments || 0).toLocaleString()), /* @__PURE__ */ React.createElement("td", { className: "py-3 text-right" }, /* @__PURE__ */ React.createElement("a", { href: item.analytics_url || item.view_url, className: "text-sky-100" }, "Open")))))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Recent comments"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (recentComments || []).map((comment) => /* @__PURE__ */ React.createElement("article", { key: comment.id, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70" }, comment.module_label), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white" }, comment.author_name, " on ", comment.item_title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, comment.body))))))); } -const __vite_glob_0_83 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_88 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioAnalytics }, Symbol.toStringTag, { value: "Module" })); @@ -90627,11 +94661,11 @@ function ConfirmDangerModal({ open, onClose, onConfirm, title, message, confirmT }, [open]); if (!open) return null; const canConfirm = input === confirmText; - const handleKeyDown = (e) => { + const handleKeyDown2 = (e) => { if (e.key === "Escape") onClose(); if (e.key === "Enter" && canConfirm) onConfirm(); }; - return /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-[60] flex items-center justify-center p-4", onKeyDown: handleKeyDown }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-black/70 backdrop-blur-sm", onClick: onClose }), /* @__PURE__ */ React.createElement("div", { className: "relative w-full max-w-md bg-nova-900 border border-red-500/30 rounded-2xl shadow-2xl shadow-red-500/10 p-6 space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "w-10 h-10 rounded-full bg-red-500/20 flex items-center justify-center flex-shrink-0" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-triangle-exclamation text-red-400" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-bold text-white" }, title), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400 mt-1" }, message))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium text-slate-400 mb-1.5 block" }, "Type ", /* @__PURE__ */ React.createElement("span", { className: "text-red-400 font-mono" }, confirmText), " to confirm"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-[60] flex items-center justify-center p-4", onKeyDown: handleKeyDown2 }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-black/70 backdrop-blur-sm", onClick: onClose }), /* @__PURE__ */ React.createElement("div", { className: "relative w-full max-w-md bg-nova-900 border border-red-500/30 rounded-2xl shadow-2xl shadow-red-500/10 p-6 space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "w-10 h-10 rounded-full bg-red-500/20 flex items-center justify-center flex-shrink-0" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-triangle-exclamation text-red-400" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-bold text-white" }, title), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400 mt-1" }, message))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium text-slate-400 mb-1.5 block" }, "Type ", /* @__PURE__ */ React.createElement("span", { className: "text-red-400 font-mono" }, confirmText), " to confirm"), /* @__PURE__ */ React.createElement( "input", { ref: inputRef, @@ -91416,7 +95450,7 @@ function StudioContentBrowser({ )); } function StudioArchived() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement( StudioContentBrowser, { @@ -91428,7 +95462,7 @@ function StudioArchived() { } )); } -const __vite_glob_0_84 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_89 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioArchived }, Symbol.toStringTag, { value: "Module" })); @@ -91445,7 +95479,7 @@ const metricCards = [ { key: "engagement_velocity", label: "Engagement Velocity", icon: "fa-bolt", color: "text-cyan-400" } ]; function StudioArtworkAnalytics() { - const { props } = X(); + const { props } = X$1(); const { artwork, analytics } = props; return /* @__PURE__ */ React.createElement(StudioLayout, { title: `Analytics: ${artwork?.title || "Artwork"}` }, /* @__PURE__ */ React.createElement( xe, @@ -91464,7 +95498,7 @@ function StudioArtworkAnalytics() { } ), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-bold text-white" }, artwork?.title), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 mt-1" }, "/", artwork?.slug))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-8" }, kpiItems$1.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "bg-nova-900/60 border border-white/10 rounded-2xl p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${item.icon} ${item.color}` }), /* @__PURE__ */ React.createElement("span", { className: "text-xs font-medium text-slate-400 uppercase tracking-wider" }, item.label)), /* @__PURE__ */ React.createElement("p", { className: "text-2xl font-bold text-white tabular-nums" }, (analytics?.[item.key] ?? 0).toLocaleString())))), /* @__PURE__ */ React.createElement("h3", { className: "text-base font-bold text-white mb-4" }, "Performance Metrics"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8" }, metricCards.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "bg-nova-900/60 border border-white/10 rounded-2xl p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 mb-3" }, /* @__PURE__ */ React.createElement("div", { className: `w-10 h-10 rounded-xl bg-white/5 flex items-center justify-center ${item.color}` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${item.icon} text-lg` })), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-medium text-slate-300" }, item.label)), /* @__PURE__ */ React.createElement("p", { className: "text-3xl font-bold text-white tabular-nums" }, (analytics?.[item.key] ?? 0).toFixed(1))))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "bg-nova-900/40 border border-white/10 rounded-2xl p-6" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white mb-3" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-line mr-2 text-slate-500" }), "Traffic Sources"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-center py-8" }, /* @__PURE__ */ React.createElement("div", { className: "text-center" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-pie text-3xl text-slate-700 mb-3" }), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500" }, "Coming soon"), /* @__PURE__ */ React.createElement("p", { className: "text-[10px] text-slate-600 mt-1" }, "Traffic source tracking is on the roadmap")))), /* @__PURE__ */ React.createElement("div", { className: "bg-nova-900/40 border border-white/10 rounded-2xl p-6" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white mb-3" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-share-from-square mr-2 text-slate-500" }), "Shares by Platform"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-center py-8" }, /* @__PURE__ */ React.createElement("div", { className: "text-center" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-share-nodes text-3xl text-slate-700 mb-3" }), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500" }, "Coming soon"), /* @__PURE__ */ React.createElement("p", { className: "text-[10px] text-slate-600 mt-1" }, "Per-platform breakdown coming in a future update")))), /* @__PURE__ */ React.createElement("div", { className: "bg-nova-900/40 border border-white/10 rounded-2xl p-6 lg:col-span-2" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white mb-3" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-trophy mr-2 text-slate-500" }), "Ranking History"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-center py-8" }, /* @__PURE__ */ React.createElement("div", { className: "text-center" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-area text-3xl text-slate-700 mb-3" }), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500" }, "Coming soon"), /* @__PURE__ */ React.createElement("p", { className: "text-[10px] text-slate-600 mt-1" }, "Historical ranking data will be tracked in a future update")))))); } -const __vite_glob_0_85 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_90 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioArtworkAnalytics }, Symbol.toStringTag, { value: "Module" })); @@ -91697,13 +95731,13 @@ function PastedTagsDialog$1({ open, preview, maxTags, onClose, onConfirm }) { }, [open]); reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [onClose, open]); if (!open || !preview) return null; const duplicateCount = preview.skippedDuplicates.length; @@ -91880,7 +95914,7 @@ function TagPicker({ setInputError(""); setPastePreview(null); }, [onChange, pastePreview, updateNameMap]); - const handleKeyDown = reactExports.useCallback((e) => { + const handleKeyDown2 = reactExports.useCallback((e) => { const commit = e.key === "Enter" || e.key === "," || e.key === "Tab"; if (!commit) return; if (e.key === "Backspace" && query === "" && selectedSlugs.length > 0) { @@ -91938,11 +95972,11 @@ function TagPicker({ SearchInput$1, { value: query, - onChange: (v) => { - setQuery(v); + onChange: (v2) => { + setQuery(v2); setInputError(""); }, - onKeyDown: handleKeyDown, + onKeyDown: handleKeyDown2, onPaste: handlePaste, inputRef, disabled, @@ -92619,7 +96653,7 @@ function RightRailCard({ title, children, className = "" }) { return /* @__PURE__ */ React.createElement("div", { className: `rounded-2xl border border-white/10 bg-[radial-gradient(circle_at_top,_rgba(148,163,184,0.16),_rgba(15,23,42,0.92)_62%)] p-4 ${className}` }, /* @__PURE__ */ React.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400" }, title), /* @__PURE__ */ React.createElement("div", { className: "mt-3" }, children)); } function StudioArtworkEdit() { - const { props } = X(); + const { props } = X$1(); const { artwork, contentTypes: rawContentTypes } = props; const groupOptions = Array.isArray(props.groupOptions) ? props.groupOptions : []; const evolutionRelationTypes = Array.isArray(props.evolutionRelationTypes) ? props.evolutionRelationTypes : []; @@ -93847,8 +97881,8 @@ function StudioArtworkEdit() { className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/70 text-[10px] text-white hover:bg-red-500/80 transition-colors", onClick: () => { URL.revokeObjectURL(url); - const next = archiveExtraScreenshots.filter((_2, j) => j !== i); - const nextPrev = archiveExtraPreviews.filter((_2, j) => j !== i); + const next = archiveExtraScreenshots.filter((_2, j2) => j2 !== i); + const nextPrev = archiveExtraPreviews.filter((_2, j2) => j2 !== i); setArchiveExtraScreenshots(next); setArchiveExtraPreviews(nextPrev); } @@ -93999,29 +98033,29 @@ function StudioArtworkEdit() { footer: /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 mr-auto" }, "Restoring creates a new version — nothing is deleted.") }, historyLoading && /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-center py-12" }, /* @__PURE__ */ React.createElement("div", { className: "w-6 h-6 border-2 border-accent/30 border-t-accent rounded-full animate-spin" })), - !historyLoading && historyData && /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, historyData.versions.map((v) => /* @__PURE__ */ React.createElement( + !historyLoading && historyData && /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, historyData.versions.map((v2) => /* @__PURE__ */ React.createElement( "div", { - key: v.id, + key: v2.id, className: [ "rounded-xl border p-4 transition-all", - v.is_current ? "border-accent/40 bg-accent/10" : "border-white/10 bg-white/[0.03] hover:bg-white/[0.06]" + v2.is_current ? "border-accent/40 bg-accent/10" : "border-white/10 bg-white/[0.03] hover:bg-white/[0.06]" ].join(" ") }, - /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold text-white" }, "v", v.version_number), v.is_current && /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-semibold px-1.5 py-0.5 rounded-full bg-accent/20 text-accent border border-accent/30" }, "Current")), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] text-slate-400" }, v.created_at ? new Date(v.created_at).toLocaleString() : ""), v.width && /* @__PURE__ */ React.createElement("p", { className: "text-[11px] text-slate-400" }, v.width, " × ", v.height, " px · ", formatBytes$1(v.file_size)), v.change_note && /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-300 mt-1 italic" }, "“", v.change_note, "”")), !v.is_current && /* @__PURE__ */ React.createElement( + /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 mb-1" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-bold text-white" }, "v", v2.version_number), v2.is_current && /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-semibold px-1.5 py-0.5 rounded-full bg-accent/20 text-accent border border-accent/30" }, "Current")), /* @__PURE__ */ React.createElement("p", { className: "text-[11px] text-slate-400" }, v2.created_at ? new Date(v2.created_at).toLocaleString() : ""), v2.width && /* @__PURE__ */ React.createElement("p", { className: "text-[11px] text-slate-400" }, v2.width, " × ", v2.height, " px · ", formatBytes$1(v2.file_size)), v2.change_note && /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-300 mt-1 italic" }, "“", v2.change_note, "”")), !v2.is_current && /* @__PURE__ */ React.createElement( Button$1, { variant: "ghost", size: "xs", - loading: restoring === v.id, - onClick: () => handleRestoreVersion(v.id) + loading: restoring === v2.id, + onClick: () => handleRestoreVersion(v2.id) }, "Restore" )) )), historyData.versions.length === 0 && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-500 text-center py-8" }, "No version history yet.")) )); } -const __vite_glob_0_86 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_91 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioArtworkEdit }, Symbol.toStringTag, { value: "Module" })); @@ -94029,11 +98063,11 @@ function SummaryCard$3({ label, value, icon }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: icon }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(value || 0).toLocaleString())); } function StudioArtworks() { - const { props } = X(); + const { props } = X$1(); const summary = props.summary || {}; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 grid gap-4 md:grid-cols-4" }, /* @__PURE__ */ React.createElement(SummaryCard$3, { label: "Artworks", value: summary.count, icon: "fa-solid fa-images" }), /* @__PURE__ */ React.createElement(SummaryCard$3, { label: "Drafts", value: summary.draft_count, icon: "fa-solid fa-file-pen" }), /* @__PURE__ */ React.createElement(SummaryCard$3, { label: "Published", value: summary.published_count, icon: "fa-solid fa-rocket" }), /* @__PURE__ */ React.createElement("a", { href: "/upload", className: "rounded-[24px] border border-sky-300/20 bg-sky-300/10 p-5 text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em]" }, "Upload artwork"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6" }, "Start a new visual upload flow without leaving Creator Studio."))), /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: props.quickCreate, hideModuleFilter: true })); } -const __vite_glob_0_87 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_92 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioArtworks }, Symbol.toStringTag, { value: "Module" })); @@ -94044,7 +98078,7 @@ function formatDate$5(value) { return date.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" }); } function StudioAssets() { - const { props } = X(); + const { props } = X$1(); const assets = props.assets || {}; const items = assets.items || []; const summary = assets.summary || []; @@ -94159,7 +98193,7 @@ function StudioAssets() { /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }) )))); } -const __vite_glob_0_88 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_93 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioAssets }, Symbol.toStringTag, { value: "Module" })); @@ -94266,7 +98300,7 @@ async function requestJson$6(url, method = "POST") { return payload; } function StudioCalendar() { - const { props } = X(); + const { props } = X$1(); const calendar = props.calendar || {}; const filters = calendar.filters || {}; const summary = calendar.summary || {}; @@ -94321,17 +98355,17 @@ function StudioCalendar() { }, [calendar.month?.days, selectedDay?.date]); reactExports.useEffect(() => { if (!selectedDay) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { setSelectedDay(null); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [selectedDay]); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Scheduled"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.scheduled_total || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Unscheduled"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.unscheduled_total || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Overloaded days"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.overloaded_days || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Next publish"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-base font-semibold text-white" }, formatReleaseCountdown(summary.next_publish_at, nowMs)), summary.next_publish_at && /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, formatScheduledDate(summary.next_publish_at)))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_35%),linear-gradient(135deg,_rgba(15,23,42,0.86),_rgba(2,6,23,0.96))] p-5 lg:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-5" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 xl:col-span-2" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search planning queue"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilters({ q: event.target.value }), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white", placeholder: "Title or module" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "View"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.view || "month", onChange: (val) => updateFilters({ view: val }), options: calendar.view_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Module"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.module || "all", onChange: (val) => updateFilters({ module: val }), options: calendar.module_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Queue"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.status || "scheduled", onChange: (val) => updateFilters({ status: val }), options: calendar.status_options || [], searchable: false })))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_340px]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, filters.view === "week" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, calendar.week?.label), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, "Week planning")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => shiftCalendar(-1), className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Prev week"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetCalendarFocus, className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Today"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => shiftCalendar(1), className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Next week"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-7" }, (calendar.week?.days || []).map((day) => /* @__PURE__ */ React.createElement("div", { key: day.date, className: "rounded-[22px] border border-white/10 bg-black/20 p-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, day.label), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, day.items.length > 0 ? day.items.map((item) => /* @__PURE__ */ React.createElement(CalendarInlineItem, { key: item.id, item })) : /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-500" }, "No scheduled items")))))) : filters.view === "agenda" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Agenda"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-4" }, (calendar.agenda || []).map((group) => /* @__PURE__ */ React.createElement("div", { key: group.date, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold text-white" }, group.label), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, group.count, " items")), /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, group.items.map((item) => /* @__PURE__ */ React.createElement(CalendarInlineItem, { key: item.id, item }))))))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, calendar.month?.label), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, "Month planning")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => shiftCalendar(-1), className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Prev month"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: resetCalendarFocus, className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Today"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => shiftCalendar(1), className: "rounded-full border border-white/10 px-3 py-1.5 text-sm text-slate-200 transition hover:border-sky-300/20 hover:bg-sky-300/10 hover:text-sky-100" }, "Next month"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid grid-cols-7 gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((label) => /* @__PURE__ */ React.createElement("div", { key: label, className: "px-2 py-1" }, label))), /* @__PURE__ */ React.createElement("div", { className: "mt-2 grid grid-cols-7 gap-2" }, (calendar.month?.days || []).map((day) => /* @__PURE__ */ React.createElement(CalendarMonthDay, { key: day.date, day, onOpenDetail: setSelectedDay }))))), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Coverage gaps"), /* @__PURE__ */ React.createElement("a", { href: "/studio/drafts", className: "text-sm font-medium text-sky-100" }, "Open drafts")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (calendar.gaps || []).length > 0 ? (calendar.gaps || []).map((gap) => /* @__PURE__ */ React.createElement("div", { key: gap.date, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-200" }, gap.label)) : /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/15 px-4 py-8 text-sm text-slate-500" }, "No empty days in the next two weeks."))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Unscheduled queue"), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, (calendar.unscheduled_items || []).length)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (calendar.unscheduled_items || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.edit_url || item.manage_url, className: "block rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, item.module_label, " · ", item.workflow?.readiness?.label || "Needs review"))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Upcoming actions"), /* @__PURE__ */ React.createElement("a", { href: "/studio/scheduled", className: "text-sm font-medium text-sky-100" }, "Open list")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (calendar.scheduled_items || []).slice(0, 5).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs font-medium text-sky-200" }, formatReleaseCountdown(item.scheduled_at, nowMs)), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, formatScheduledDate(item.scheduled_at)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", disabled: busyKey === `publish:${item.id}`, onClick: () => runAction(props.endpoints.publishNowPattern, item, "publish"), className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-xs text-sky-100 disabled:opacity-50" }, "Publish now"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: busyKey === `unschedule:${item.id}`, onClick: () => runAction(props.endpoints.unschedulePattern, item, "unschedule"), className: "rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-200 disabled:opacity-50" }, "Unschedule")))))))), /* @__PURE__ */ React.createElement(CalendarDayModal, { day: selectedDay, busyKey, endpoints: props.endpoints, onAction: runAction, onClose: () => setSelectedDay(null), nowMs }))); } -const __vite_glob_0_89 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_94 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioCalendar }, Symbol.toStringTag, { value: "Module" })); @@ -94349,11 +98383,11 @@ const secondaryItems = [ { key: "downloads", label: "Downloads", icon: "fa-download" } ]; function StudioCardAnalytics() { - const { props } = X(); + const { props } = X$1(); const { card, analytics } = props; return /* @__PURE__ */ React.createElement(StudioLayout, { title: `Analytics: ${card?.title || "Nova Card"}` }, /* @__PURE__ */ React.createElement(xe, { href: "/studio/cards", className: "mb-6 inline-flex items-center gap-2 text-sm text-slate-400 transition-colors hover:text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left" }), "Back to Cards"), /* @__PURE__ */ React.createElement("div", { className: "mb-8 flex items-center gap-4 rounded-2xl border border-white/10 bg-nova-900/60 p-4" }, card?.preview_url ? /* @__PURE__ */ React.createElement("img", { src: card.preview_url, alt: card.title, className: "h-20 w-20 rounded-xl object-cover bg-nova-800" }) : null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-bold text-white" }, card?.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs text-slate-500" }, "/", card?.slug), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-xs uppercase tracking-[0.18em] text-slate-400" }, card?.status, " • ", card?.visibility))), /* @__PURE__ */ React.createElement("div", { className: "mb-8 grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-6" }, kpiItems.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "rounded-2xl border border-white/10 bg-nova-900/60 p-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${item.icon} ${item.color}` }), /* @__PURE__ */ React.createElement("span", { className: "text-xs font-medium uppercase tracking-wider text-slate-400" }, item.label)), /* @__PURE__ */ React.createElement("p", { className: "text-2xl font-bold tabular-nums text-white" }, (analytics?.[item.key] ?? 0).toLocaleString())))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-nova-900/40 p-6" }, /* @__PURE__ */ React.createElement("h3", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.18em] text-slate-300" }, "Ranking signals"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.18em] text-slate-400" }, "Trending score"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-bold tabular-nums text-white" }, Number(analytics?.trending_score ?? 0).toFixed(2))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/[0.03] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.18em] text-slate-400" }, "Last engaged"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-white" }, analytics?.last_engaged_at || "No activity yet")))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-nova-900/40 p-6" }, /* @__PURE__ */ React.createElement("h3", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.18em] text-slate-300" }, "Secondary metrics"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, secondaryItems.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${item.icon} text-slate-500` }), item.label), /* @__PURE__ */ React.createElement("div", { className: "text-base font-semibold tabular-nums text-white" }, (analytics?.[item.key] ?? 0).toLocaleString()))))))); } -const __vite_glob_0_90 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_95 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioCardAnalytics }, Symbol.toStringTag, { value: "Module" })); @@ -94898,7 +98932,7 @@ function apiUrl(pattern, id) { return fillPattern(pattern, { CARD: id }); } function StudioCardEditor() { - const { props } = X(); + const { props } = X$1(); const editorOptions = props.editorOptions || {}; const endpoints = props.endpoints || {}; const previewMode = Boolean(props.previewMode); @@ -95172,7 +99206,7 @@ function StudioCardEditor() { }); }, 2500); } - function handleElementMove(elementId, x, y, widthPct) { + function handleElementMove(elementId, x2, y2, widthPct) { if (elementId.startsWith("block:")) { const key = elementId.slice(6); setCard((current) => { @@ -95180,7 +99214,7 @@ function StudioCardEditor() { const idx = blocks.findIndex((b2) => b2.key === key || b2.type === key); if (idx === -1) return current; const updated = [...blocks]; - updated[idx] = { ...updated[idx], pos_x: x, pos_y: y, ...widthPct != null ? { pos_width: widthPct } : {} }; + updated[idx] = { ...updated[idx], pos_x: x2, pos_y: y2, ...widthPct != null ? { pos_width: widthPct } : {} }; return { ...current, project_json: { ...current.project_json, text_blocks: updated } }; }); } else if (elementId.startsWith("decoration:")) { @@ -95189,7 +99223,7 @@ function StudioCardEditor() { const decorations2 = Array.isArray(current.project_json?.decorations) ? [...current.project_json.decorations] : []; if (idx < 0 || idx >= decorations2.length) return current; const updated = [...decorations2]; - updated[idx] = { ...updated[idx], pos_x: x, pos_y: y }; + updated[idx] = { ...updated[idx], pos_x: x2, pos_y: y2 }; return { ...current, project_json: { ...current.project_json, decorations: updated } }; }); } @@ -95402,7 +99436,7 @@ function StudioCardEditor() { function tabBtnClasses(active) { return `flex-none whitespace-nowrap rounded-2xl border px-4 py-2 text-sm font-semibold transition ${active ? "border-sky-400/30 bg-sky-500/15 text-sky-200" : "border-transparent text-slate-400 hover:bg-white/[0.05] hover:text-white"}`; } - return /* @__PURE__ */ React.createElement(StudioLayout, { title: previewMode ? "Card Preview" : "Card Editor" }, /* @__PURE__ */ React.createElement(Se, { title: previewMode ? "Nova Card Preview" : "Nova Card Editor" }), /* @__PURE__ */ React.createElement("section", { className: "mb-5 flex flex-wrap items-center justify-between gap-3 rounded-[24px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.10),transparent_40%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] px-5 py-4 shadow-[0_18px_48px_rgba(2,6,23,0.28)]" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/70" }, "Nova Cards editor"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 truncate text-xl font-semibold tracking-[-0.03em] text-white" }, card.title || "Untitled card"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap items-center gap-1.5" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setEditorMode("quick"), className: pillClasses(editorMode === "quick") }, "Quick mode"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setEditorMode("full"), className: pillClasses(editorMode === "full") }, "Advanced mode"))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement(NovaCardAutosaveIndicator, { status: autosaveStatus, message: autosaveMessage }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: manualSave, disabled: busy || !cardId, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08] disabled:opacity-60" }, "Save"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: publishCard, disabled: busy || !cardId, className: "rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100 transition hover:bg-emerald-400/15 disabled:opacity-60" }, "Publish"), card.public_url ? /* @__PURE__ */ React.createElement("a", { href: card.public_url, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Public page") : null)), /* @__PURE__ */ React.createElement("div", { className: "xl:grid xl:grid-cols-[minmax(0,480px)_minmax(0,1fr)] xl:items-start xl:gap-6" }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 xl:mb-0" }, /* @__PURE__ */ React.createElement("div", { className: "no-scrollbar -mx-1 flex gap-1 overflow-x-auto px-1 pb-2" }, editorTabs.map((tab2) => /* @__PURE__ */ React.createElement("button", { key: tab2.key, type: "button", onClick: () => setActiveTab(tab2.key), className: tabBtnClasses(activeTab === tab2.key) }, tab2.label))), /* @__PURE__ */ React.createElement("div", { className: "mt-2 rounded-[28px] border border-white/10 bg-white/[0.04] p-5" }, activeTab === "background" && /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Canvas format"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.formats || []).map((format) => /* @__PURE__ */ React.createElement("button", { key: format.key, type: "button", onClick: () => updateCard({ format: format.key }), className: pillClasses((card.format || "square") === format.key) }, format.label)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Template"), /* @__PURE__ */ React.createElement(NovaCardTemplatePicker, { templates: editorOptions.templates || [], selectedId: card.template_id, onSelect: handleTemplateSelect })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Background type"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.background_modes || []).map((mode) => /* @__PURE__ */ React.createElement("button", { key: mode.key, type: "button", onClick: () => updateCard({ background_type: mode.key }, { background: { type: mode.key } }), className: pillClasses(backgroundType === mode.key) }, mode.label)))), (backgroundType === "gradient" || backgroundType === "template") && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Gradient"), /* @__PURE__ */ React.createElement(NovaCardGradientPicker, { gradients: editorOptions.gradient_presets || [], selectedKey: card.project_json?.background?.gradient_preset, onSelect: handleGradientSelect })), backgroundType === "solid" && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Solid color"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("input", { type: "color", value: card.project_json?.background?.solid_color || "#111827", onChange: (event) => updateCard({ background_type: "solid" }, { background: { type: "solid", solid_color: event.target.value } }), className: "h-12 w-16 cursor-pointer rounded-xl border border-white/10 bg-[#0d1726] p-1.5" }), /* @__PURE__ */ React.createElement("span", { className: "font-mono text-sm text-white/60" }, card.project_json?.background?.solid_color || "#111827"))), backgroundType === "upload" && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Custom image"), card.background_image?.processed_url ? /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[18px] border border-white/10" }, /* @__PURE__ */ React.createElement("img", { src: card.background_image.processed_url, alt: "Uploaded background", className: "h-44 w-full object-cover" }), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between bg-white/[0.03] px-4 py-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-semibold uppercase tracking-[0.16em] text-emerald-300" }, "✓ Uploaded"), /* @__PURE__ */ React.createElement("label", { className: "cursor-pointer text-xs text-slate-400 transition hover:text-white" }, "Replace image", /* @__PURE__ */ React.createElement("input", { type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: uploadBackground, disabled: uploading })))) : /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer flex-col items-center gap-3 rounded-[22px] border-2 border-dashed px-6 py-10 text-center transition ${uploading ? "border-sky-400/40 bg-sky-400/[0.04]" : "border-white/12 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("div", { className: "text-3xl" }, uploading ? "⏳" : "🖼️"), /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-slate-300" }, uploading ? "Uploading…" : "Choose background image"), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-500" }, "JPG, PNG or WebP · Max 8 MB · Min 480×480 px"), /* @__PURE__ */ React.createElement("input", { type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: uploadBackground, disabled: uploading }))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Overlay & depth"), /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Overlay style"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.background?.overlay_style || "dark-soft", onChange: (val) => updateCard({}, { background: { overlay_style: val } }), options: (overlayOptions || []).map((o) => ({ value: o.value, label: o.label })) })), /* @__PURE__ */ React.createElement("label", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex justify-between" }, /* @__PURE__ */ React.createElement("span", null, "Overlay opacity"), /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, card.project_json?.background?.opacity || 50, "%")), /* @__PURE__ */ React.createElement("input", { type: "range", min: "0", max: "90", step: "10", value: card.project_json?.background?.opacity || 50, onChange: (event) => updateCard({}, { background: { opacity: Number(event.target.value) } }), className: "w-full" })), advancedMode && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex justify-between" }, /* @__PURE__ */ React.createElement("span", null, "Background blur"), /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, card.project_json?.background?.blur_level || 0, "px")), /* @__PURE__ */ React.createElement("input", { type: "range", min: "0", max: "32", step: "4", value: card.project_json?.background?.blur_level || 0, onChange: (event) => updateCard({}, { background: { blur_level: Number(event.target.value) } }), className: "w-full" })), /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Focal position"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.background?.focal_position || "center", onChange: (val) => updateCard({}, { background: { focal_position: val } }), options: (editorOptions.focal_positions || []).map((p) => ({ value: p.key, label: p.label })) }))))), advancedMode && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Decorations"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-5" }, (editorOptions.decor_presets || []).map((decor) => /* @__PURE__ */ React.createElement("button", { key: decor.key, type: "button", onClick: () => addDecoration(decor), className: "flex flex-col items-center gap-1 rounded-[18px] border border-white/10 bg-white/[0.03] px-2 py-3 text-center transition hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("span", { className: "text-2xl" }, decor.glyph), /* @__PURE__ */ React.createElement("span", { className: "text-[10px] text-slate-400" }, decor.label)))), decorations.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, decorations.map((decor, index2) => /* @__PURE__ */ React.createElement("div", { key: `${decor.key}-${index2}`, className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("span", { className: "text-base" }, decor.glyph), /* @__PURE__ */ React.createElement("span", { className: "flex-1 px-3 text-slate-300" }, decor.key), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeDecoration(index2), className: "text-rose-300 transition hover:text-rose-200" }, "Remove")), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-[10px] uppercase tracking-[0.16em] text-slate-500 w-14 shrink-0" }, "Opacity"), /* @__PURE__ */ React.createElement( + return /* @__PURE__ */ React.createElement(StudioLayout, { title: previewMode ? "Card Preview" : "Card Editor" }, /* @__PURE__ */ React.createElement(Se$1, { title: previewMode ? "Nova Card Preview" : "Nova Card Editor" }), /* @__PURE__ */ React.createElement("section", { className: "mb-5 flex flex-wrap items-center justify-between gap-3 rounded-[24px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.10),transparent_40%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] px-5 py-4 shadow-[0_18px_48px_rgba(2,6,23,0.28)]" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/70" }, "Nova Cards editor"), /* @__PURE__ */ React.createElement("h2", { className: "mt-1 truncate text-xl font-semibold tracking-[-0.03em] text-white" }, card.title || "Untitled card"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap items-center gap-1.5" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setEditorMode("quick"), className: pillClasses(editorMode === "quick") }, "Quick mode"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setEditorMode("full"), className: pillClasses(editorMode === "full") }, "Advanced mode"))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement(NovaCardAutosaveIndicator, { status: autosaveStatus, message: autosaveMessage }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: manualSave, disabled: busy || !cardId, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08] disabled:opacity-60" }, "Save"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: publishCard, disabled: busy || !cardId, className: "rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100 transition hover:bg-emerald-400/15 disabled:opacity-60" }, "Publish"), card.public_url ? /* @__PURE__ */ React.createElement("a", { href: card.public_url, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, "Public page") : null)), /* @__PURE__ */ React.createElement("div", { className: "xl:grid xl:grid-cols-[minmax(0,480px)_minmax(0,1fr)] xl:items-start xl:gap-6" }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 xl:mb-0" }, /* @__PURE__ */ React.createElement("div", { className: "no-scrollbar -mx-1 flex gap-1 overflow-x-auto px-1 pb-2" }, editorTabs.map((tab2) => /* @__PURE__ */ React.createElement("button", { key: tab2.key, type: "button", onClick: () => setActiveTab(tab2.key), className: tabBtnClasses(activeTab === tab2.key) }, tab2.label))), /* @__PURE__ */ React.createElement("div", { className: "mt-2 rounded-[28px] border border-white/10 bg-white/[0.04] p-5" }, activeTab === "background" && /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Canvas format"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.formats || []).map((format) => /* @__PURE__ */ React.createElement("button", { key: format.key, type: "button", onClick: () => updateCard({ format: format.key }), className: pillClasses((card.format || "square") === format.key) }, format.label)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Template"), /* @__PURE__ */ React.createElement(NovaCardTemplatePicker, { templates: editorOptions.templates || [], selectedId: card.template_id, onSelect: handleTemplateSelect })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Background type"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.background_modes || []).map((mode) => /* @__PURE__ */ React.createElement("button", { key: mode.key, type: "button", onClick: () => updateCard({ background_type: mode.key }, { background: { type: mode.key } }), className: pillClasses(backgroundType === mode.key) }, mode.label)))), (backgroundType === "gradient" || backgroundType === "template") && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Gradient"), /* @__PURE__ */ React.createElement(NovaCardGradientPicker, { gradients: editorOptions.gradient_presets || [], selectedKey: card.project_json?.background?.gradient_preset, onSelect: handleGradientSelect })), backgroundType === "solid" && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Solid color"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("input", { type: "color", value: card.project_json?.background?.solid_color || "#111827", onChange: (event) => updateCard({ background_type: "solid" }, { background: { type: "solid", solid_color: event.target.value } }), className: "h-12 w-16 cursor-pointer rounded-xl border border-white/10 bg-[#0d1726] p-1.5" }), /* @__PURE__ */ React.createElement("span", { className: "font-mono text-sm text-white/60" }, card.project_json?.background?.solid_color || "#111827"))), backgroundType === "upload" && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Custom image"), card.background_image?.processed_url ? /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-[18px] border border-white/10" }, /* @__PURE__ */ React.createElement("img", { src: card.background_image.processed_url, alt: "Uploaded background", className: "h-44 w-full object-cover" }), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between bg-white/[0.03] px-4 py-2.5" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-semibold uppercase tracking-[0.16em] text-emerald-300" }, "✓ Uploaded"), /* @__PURE__ */ React.createElement("label", { className: "cursor-pointer text-xs text-slate-400 transition hover:text-white" }, "Replace image", /* @__PURE__ */ React.createElement("input", { type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: uploadBackground, disabled: uploading })))) : /* @__PURE__ */ React.createElement("label", { className: `flex cursor-pointer flex-col items-center gap-3 rounded-[22px] border-2 border-dashed px-6 py-10 text-center transition ${uploading ? "border-sky-400/40 bg-sky-400/[0.04]" : "border-white/12 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]"}` }, /* @__PURE__ */ React.createElement("div", { className: "text-3xl" }, uploading ? "⏳" : "🖼️"), /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-slate-300" }, uploading ? "Uploading…" : "Choose background image"), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-500" }, "JPG, PNG or WebP · Max 8 MB · Min 480×480 px"), /* @__PURE__ */ React.createElement("input", { type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: uploadBackground, disabled: uploading }))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Overlay & depth"), /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Overlay style"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.background?.overlay_style || "dark-soft", onChange: (val) => updateCard({}, { background: { overlay_style: val } }), options: (overlayOptions || []).map((o) => ({ value: o.value, label: o.label })) })), /* @__PURE__ */ React.createElement("label", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex justify-between" }, /* @__PURE__ */ React.createElement("span", null, "Overlay opacity"), /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, card.project_json?.background?.opacity || 50, "%")), /* @__PURE__ */ React.createElement("input", { type: "range", min: "0", max: "90", step: "10", value: card.project_json?.background?.opacity || 50, onChange: (event) => updateCard({}, { background: { opacity: Number(event.target.value) } }), className: "w-full" })), advancedMode && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex justify-between" }, /* @__PURE__ */ React.createElement("span", null, "Background blur"), /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, card.project_json?.background?.blur_level || 0, "px")), /* @__PURE__ */ React.createElement("input", { type: "range", min: "0", max: "32", step: "4", value: card.project_json?.background?.blur_level || 0, onChange: (event) => updateCard({}, { background: { blur_level: Number(event.target.value) } }), className: "w-full" })), /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Focal position"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.background?.focal_position || "center", onChange: (val) => updateCard({}, { background: { focal_position: val } }), options: (editorOptions.focal_positions || []).map((p) => ({ value: p.key, label: p.label })) }))))), advancedMode && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Decorations"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-5" }, (editorOptions.decor_presets || []).map((decor) => /* @__PURE__ */ React.createElement("button", { key: decor.key, type: "button", onClick: () => addDecoration(decor), className: "flex flex-col items-center gap-1 rounded-[18px] border border-white/10 bg-white/[0.03] px-2 py-3 text-center transition hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("span", { className: "text-2xl" }, decor.glyph), /* @__PURE__ */ React.createElement("span", { className: "text-[10px] text-slate-400" }, decor.label)))), decorations.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "mt-3 space-y-2" }, decorations.map((decor, index2) => /* @__PURE__ */ React.createElement("div", { key: `${decor.key}-${index2}`, className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("span", { className: "text-base" }, decor.glyph), /* @__PURE__ */ React.createElement("span", { className: "flex-1 px-3 text-slate-300" }, decor.key), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeDecoration(index2), className: "text-rose-300 transition hover:text-rose-200" }, "Remove")), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-[10px] uppercase tracking-[0.16em] text-slate-500 w-14 shrink-0" }, "Opacity"), /* @__PURE__ */ React.createElement( "input", { type: "range", @@ -95471,15 +99505,15 @@ function StudioCardEditor() { className: `flex-1 rounded-2xl border py-2.5 text-sm font-semibold transition ${(card.project_json?.layout?.alignment || "center") === preset.key ? "border-sky-400/30 bg-sky-500/15 text-sky-200" : "border-white/10 bg-white/[0.03] text-slate-300 hover:bg-white/[0.05]"}` }, preset.label - )))), advancedMode && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Vertical position"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.position_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { position: preset.key } }), className: pillClasses((card.project_json?.layout?.position || "center") === preset.key) }, preset.label)))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Padding"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2" }, (editorOptions.padding_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { padding: preset.key } }), className: pillClasses((card.project_json?.layout?.padding || "comfortable") === preset.key) }, preset.label)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Text width"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2" }, (editorOptions.max_width_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { max_width: preset.key } }), className: pillClasses((card.project_json?.layout?.max_width || "balanced") === preset.key) }, preset.label))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Frame & effects"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block" }, "Frame"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.frame?.preset || "none", onChange: (val) => updateCard({}, { frame: { preset: val } }), options: (editorOptions.frame_presets || []).map((p) => ({ value: p.key, label: p.label })) })), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block text-xs" }, "Color grade"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.effects?.color_grade || "none", onChange: (val) => updateCard({}, { effects: { color_grade: val } }), options: (editorOptions.color_grade_presets || []).map((p) => ({ value: p.key, label: p.label })) })), /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block text-xs" }, "Effect"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.effects?.effect_preset || "none", onChange: (val) => updateCard({}, { effects: { effect_preset: val } }), options: (editorOptions.effect_presets || []).map((p) => ({ value: p.key, label: p.label })) })))))), advancedMode && (Object.keys(creatorPresets).length > 0 || endpoints.presetsIndex) && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Creator presets"), /* @__PURE__ */ React.createElement(NovaCardPresetPicker, { presets: creatorPresets, endpoints, cardId, onApplyPatch: handleApplyPresetPatch, onPresetsChange: reloadPresets })), advancedMode && endpoints.aiSuggestPattern && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "AI assist"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: loadingAi || !cardId, onClick: fetchAiSuggestions, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1.5 text-xs font-semibold text-sky-200 transition hover:bg-sky-400/15 disabled:opacity-50" }, loadingAi ? "Analysing…" : "Suggest")), aiSuggestions ? /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, aiSuggestions.tags?.length > 0 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Suggested tags"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, aiSuggestions.tags.map((tag) => /* @__PURE__ */ React.createElement("button", { key: tag, type: "button", onClick: () => applyAiTagSuggestions([tag]), className: "rounded-full border border-sky-400/20 bg-sky-400/10 px-3 py-1 text-xs font-semibold text-sky-200 transition hover:bg-sky-400/15" }, "+ ", tag))), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => applyAiTagSuggestions(aiSuggestions.tags), className: "mt-1.5 text-xs text-slate-400 underline transition hover:text-white" }, "Add all")), aiSuggestions.mood && /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.03] px-3 py-2.5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Mood"), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-white" }, aiSuggestions.mood)), aiSuggestions.layout_suggestion && /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-3 py-2.5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Layout suggestion"), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-white" }, aiSuggestions.layout_suggestion)), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateCard({}, { layout: { layout: aiSuggestions.layout_suggestion } }), className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold text-white transition hover:bg-white/[0.08]" }, "Apply")), (aiSuggestions.readability_fixes || []).map((fix, fi) => /* @__PURE__ */ React.createElement("div", { key: fi, className: "rounded-xl border border-amber-400/15 bg-amber-400/[0.06] px-3 py-2.5 text-sm text-amber-100" }, fix))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-500" }, "Run a suggestion pass to see AI-powered tips for this card.")))), activeTab === "publish" && /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Category"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(card.category_id || ""), onChange: (val) => updateCard({ category_id: val ? Number(val) : null }), placeholder: "Select category", options: (editorOptions.categories || []).map((c) => ({ value: String(c.id), label: c.name })) })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-2 text-sm text-slate-300" }, "Visibility"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, ["private", "unlisted", "public"].map((v) => /* @__PURE__ */ React.createElement( + )))), advancedMode && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Vertical position"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, (editorOptions.position_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { position: preset.key } }), className: pillClasses((card.project_json?.layout?.position || "center") === preset.key) }, preset.label)))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Padding"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2" }, (editorOptions.padding_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { padding: preset.key } }), className: pillClasses((card.project_json?.layout?.padding || "comfortable") === preset.key) }, preset.label)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Text width"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2" }, (editorOptions.max_width_presets || []).map((preset) => /* @__PURE__ */ React.createElement("button", { key: preset.key, type: "button", onClick: () => updateCard({}, { layout: { max_width: preset.key } }), className: pillClasses((card.project_json?.layout?.max_width || "balanced") === preset.key) }, preset.label))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Frame & effects"), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block" }, "Frame"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.frame?.preset || "none", onChange: (val) => updateCard({}, { frame: { preset: val } }), options: (editorOptions.frame_presets || []).map((p) => ({ value: p.key, label: p.label })) })), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block text-xs" }, "Color grade"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.effects?.color_grade || "none", onChange: (val) => updateCard({}, { effects: { color_grade: val } }), options: (editorOptions.color_grade_presets || []).map((p) => ({ value: p.key, label: p.label })) })), /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-1.5 block text-xs" }, "Effect"), /* @__PURE__ */ React.createElement(NovaSelect, { value: card.project_json?.effects?.effect_preset || "none", onChange: (val) => updateCard({}, { effects: { effect_preset: val } }), options: (editorOptions.effect_presets || []).map((p) => ({ value: p.key, label: p.label })) })))))), advancedMode && (Object.keys(creatorPresets).length > 0 || endpoints.presetsIndex) && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "Creator presets"), /* @__PURE__ */ React.createElement(NovaCardPresetPicker, { presets: creatorPresets, endpoints, cardId, onApplyPatch: handleApplyPresetPatch, onPresetsChange: reloadPresets })), advancedMode && endpoints.aiSuggestPattern && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-3 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400" }, "AI assist"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: loadingAi || !cardId, onClick: fetchAiSuggestions, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1.5 text-xs font-semibold text-sky-200 transition hover:bg-sky-400/15 disabled:opacity-50" }, loadingAi ? "Analysing…" : "Suggest")), aiSuggestions ? /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, aiSuggestions.tags?.length > 0 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Suggested tags"), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, aiSuggestions.tags.map((tag) => /* @__PURE__ */ React.createElement("button", { key: tag, type: "button", onClick: () => applyAiTagSuggestions([tag]), className: "rounded-full border border-sky-400/20 bg-sky-400/10 px-3 py-1 text-xs font-semibold text-sky-200 transition hover:bg-sky-400/15" }, "+ ", tag))), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => applyAiTagSuggestions(aiSuggestions.tags), className: "mt-1.5 text-xs text-slate-400 underline transition hover:text-white" }, "Add all")), aiSuggestions.mood && /* @__PURE__ */ React.createElement("div", { className: "rounded-xl border border-white/10 bg-white/[0.03] px-3 py-2.5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Mood"), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-white" }, aiSuggestions.mood)), aiSuggestions.layout_suggestion && /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-3 py-2.5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Layout suggestion"), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-white" }, aiSuggestions.layout_suggestion)), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateCard({}, { layout: { layout: aiSuggestions.layout_suggestion } }), className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold text-white transition hover:bg-white/[0.08]" }, "Apply")), (aiSuggestions.readability_fixes || []).map((fix, fi) => /* @__PURE__ */ React.createElement("div", { key: fi, className: "rounded-xl border border-amber-400/15 bg-amber-400/[0.06] px-3 py-2.5 text-sm text-amber-100" }, fix))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-500" }, "Run a suggestion pass to see AI-powered tips for this card.")))), activeTab === "publish" && /* @__PURE__ */ React.createElement("div", { className: "space-y-5" }, /* @__PURE__ */ React.createElement("div", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Category"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(card.category_id || ""), onChange: (val) => updateCard({ category_id: val ? Number(val) : null }), placeholder: "Select category", options: (editorOptions.categories || []).map((c) => ({ value: String(c.id), label: c.name })) })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "mb-2 text-sm text-slate-300" }, "Visibility"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, ["private", "unlisted", "public"].map((v2) => /* @__PURE__ */ React.createElement( "button", { - key: v, + key: v2, type: "button", - onClick: () => updateCard({ visibility: v }), - className: `flex-1 rounded-2xl border py-2.5 text-sm font-semibold capitalize transition ${(card.visibility || "private") === v ? "border-sky-400/30 bg-sky-500/15 text-sky-200" : "border-white/10 bg-white/[0.03] text-slate-300 hover:bg-white/[0.05]"}` + onClick: () => updateCard({ visibility: v2 }), + className: `flex-1 rounded-2xl border py-2.5 text-sm font-semibold capitalize transition ${(card.visibility || "private") === v2 ? "border-sky-400/30 bg-sky-500/15 text-sky-200" : "border-white/10 bg-white/[0.03] text-slate-300 hover:bg-white/[0.05]"}` }, - v + v2 )))), /* @__PURE__ */ React.createElement("label", { className: "block text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "mb-2 block" }, "Tags"), /* @__PURE__ */ React.createElement("input", { value: tagInput, onChange: (event) => setTagInput(event.target.value), placeholder: "motivation, calm, poetry", className: "w-full rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-white outline-none transition focus:border-sky-300/35" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, [ { key: "allow_download", label: "Allow download" }, { key: "allow_remix", label: "Allow remix" }, @@ -95497,7 +99531,7 @@ function StudioCardEditor() { fmt2.label ))), exportStatus && /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex items-center gap-3 rounded-[18px] border border-white/10 bg-white/[0.03] p-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Export status"), /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold capitalize text-white" }, exportStatus.status)), exportStatus.status === "ready" && exportStatus.output_url && /* @__PURE__ */ React.createElement("a", { href: exportStatus.output_url, download: true, className: "ml-auto rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1.5 text-xs font-semibold text-emerald-200 transition hover:bg-emerald-400/15" }, "Download"))))), /* @__PURE__ */ React.createElement("nav", { className: "sticky bottom-0 z-20 mt-6 border-t border-white/10 bg-[rgba(2,6,23,0.92)] px-4 py-3 backdrop-blur xl:hidden" }, /* @__PURE__ */ React.createElement("div", { className: "mx-auto flex max-w-7xl items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => goToNextTab(-1), disabled: tabIndex === 0, className: "rounded-2xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08] disabled:opacity-50" }, "Back"), /* @__PURE__ */ React.createElement("div", { className: "text-center" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Step ", tabIndex + 1, " / ", editorTabs.length), /* @__PURE__ */ React.createElement("div", { className: "mt-0.5 text-sm font-semibold text-white" }, editorTabs[tabIndex]?.label)), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => goToNextTab(1), disabled: tabIndex >= editorTabs.length - 1, className: "rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2.5 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:opacity-50" }, "Next")))); } -const __vite_glob_0_91 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_96 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioCardEditor }, Symbol.toStringTag, { value: "Module" })); @@ -95505,11 +99539,11 @@ function StatCard$1({ label, value, icon }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.04] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-12 w-12 items-center justify-center rounded-2xl border border-sky-300/20 bg-sky-400/10 text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon}` })), /* @__PURE__ */ React.createElement("span", { className: "text-3xl font-semibold tracking-[-0.04em] text-white" }, value))); } function StudioCardsIndex() { - const { props } = X(); + const { props } = X$1(); const summary = props.summary || {}; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.15),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75" }, "Creation surface"), /* @__PURE__ */ React.createElement("h2", { className: "mt-3 text-3xl font-semibold tracking-[-0.04em] text-white" }, "Build quote cards, mood cards, and visual text art."), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-7 text-slate-300" }, "Cards now live inside the same shared Creator Studio queue as artworks, collections, and stories, while keeping the dedicated editor and analytics flow.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement("a", { href: "/studio/cards/create", className: "inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-plus" }), "New card"), /* @__PURE__ */ React.createElement("a", { href: props.publicBrowseUrl, className: "inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-compass" }), "Browse public cards")))), /* @__PURE__ */ React.createElement("section", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement(StatCard$1, { label: "All cards", value: summary.count || 0, icon: "fa-layer-group" }), /* @__PURE__ */ React.createElement(StatCard$1, { label: "Drafts", value: summary.draft_count || 0, icon: "fa-file-lines" }), /* @__PURE__ */ React.createElement(StatCard$1, { label: "Archived", value: summary.archived_count || 0, icon: "fa-box-archive" }), /* @__PURE__ */ React.createElement(StatCard$1, { label: "Published", value: summary.published_count || 0, icon: "fa-earth-americas" })), /* @__PURE__ */ React.createElement("section", { className: "mt-8" }, /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: props.quickCreate, hideModuleFilter: true, emptyTitle: "No cards yet", emptyBody: "Create your first Nova card and it will appear here alongside your other Creator Studio content." }))); } -const __vite_glob_0_92 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_97 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioCardsIndex }, Symbol.toStringTag, { value: "Module" })); @@ -95528,7 +99562,7 @@ function formatDate$4(value) { return date.toLocaleDateString(void 0, { month: "short", day: "numeric" }); } function StudioChallenges() { - const { props } = X(); + const { props } = X$1(); const { summary, spotlight, activeChallenges, recentEntries, cardLeaders, reminders } = props; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-4 xl:grid-cols-6" }, summaryCards$1.map(([key, label, icon]) => /* @__PURE__ */ React.createElement("div", { key, className: "rounded-[26px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon} text-sky-200` })), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(summary?.[key] || 0).toLocaleString())))), spotlight ? /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_34%),radial-gradient(circle_at_bottom_right,_rgba(250,204,21,0.12),_transparent_40%),linear-gradient(135deg,_rgba(15,23,42,0.88),_rgba(2,6,23,0.96))] p-6 shadow-[0_22px_60px_rgba(2,6,23,0.28)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-3xl" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/70" }, "Challenge spotlight"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-3xl font-semibold text-white" }, spotlight.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, spotlight.prompt || spotlight.description || "A featured challenge run is active in Nova Cards right now."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-3 text-xs uppercase tracking-[0.16em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, spotlight.status), /* @__PURE__ */ React.createElement("span", null, spotlight.official ? "Official" : "Community"), /* @__PURE__ */ React.createElement("span", null, spotlight.entries_count, " entries"), /* @__PURE__ */ React.createElement("span", null, spotlight.is_joined ? `${spotlight.submission_count} submitted` : "Not joined yet"))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-3" }, /* @__PURE__ */ React.createElement( "a", @@ -95582,7 +99616,7 @@ function StudioChallenges() { "Challenge" ), /* @__PURE__ */ React.createElement("a", { href: entry.card.edit_url, className: "text-slate-300" }, "Edit card"), /* @__PURE__ */ React.createElement("a", { href: entry.card.analytics_url, className: "text-slate-300" }, "Analytics")))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Cards with challenge traction"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (cardLeaders || []).map((card) => /* @__PURE__ */ React.createElement("div", { key: card.id, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, card.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-500" }, card.status, " • ", card.challenge_entries_count, " challenge entries")), /* @__PURE__ */ React.createElement("a", { href: card.edit_url, className: "text-xs font-semibold uppercase tracking-[0.16em] text-sky-100" }, "Open")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid grid-cols-2 gap-3 text-sm text-slate-400" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Views"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, Number(card.views_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Comments"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, Number(card.comments_count || 0).toLocaleString()))))))))); } -const __vite_glob_0_93 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_98 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioChallenges }, Symbol.toStringTag, { value: "Module" })); @@ -95590,11 +99624,11 @@ function SummaryCard$2({ label, value, icon }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: icon }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(value || 0).toLocaleString())); } function StudioCollections() { - const { props } = X(); + const { props } = X$1(); const summary = props.summary || {}; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 grid gap-4 md:grid-cols-4" }, /* @__PURE__ */ React.createElement(SummaryCard$2, { label: "Collections", value: summary.count, icon: "fa-solid fa-layer-group" }), /* @__PURE__ */ React.createElement(SummaryCard$2, { label: "Drafts", value: summary.draft_count, icon: "fa-solid fa-file-pen" }), /* @__PURE__ */ React.createElement(SummaryCard$2, { label: "Published", value: summary.published_count, icon: "fa-solid fa-rocket" }), /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "rounded-[24px] border border-sky-300/20 bg-sky-300/10 p-5 text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em]" }, "Collection dashboard"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6" }, "Open the full collection workflow surface for rules, history, and collaboration."))), /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: props.quickCreate, hideModuleFilter: true })); } -const __vite_glob_0_94 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_99 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioCollections }, Symbol.toStringTag, { value: "Module" })); @@ -95630,7 +99664,7 @@ async function requestJson$5(url, method, body2) { return payload; } function StudioComments() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const filters = listing.filters || {}; const items = listing.items || []; @@ -95881,12 +99915,12 @@ function StudioComments() { /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }) ))))); } -const __vite_glob_0_95 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_100 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioComments }, Symbol.toStringTag, { value: "Module" })); function StudioContentIndex() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement( StudioContentBrowser, { @@ -95897,7 +99931,7 @@ function StudioContentIndex() { } )); } -const __vite_glob_0_96 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_101 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioContentIndex }, Symbol.toStringTag, { value: "Module" })); @@ -95986,7 +100020,7 @@ function RecentComment({ comment }) { return /* @__PURE__ */ React.createElement("div", { className: "border-b border-white/5 py-3 last:border-0" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm text-white" }, /* @__PURE__ */ React.createElement("span", { className: "font-medium text-sky-100" }, comment.author_name), " ", "on", " ", /* @__PURE__ */ React.createElement("span", { className: "text-slate-300" }, comment.item_title)), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs text-slate-500 line-clamp-2" }, comment.body), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-[10px] text-slate-600" }, new Date(comment.created_at).toLocaleDateString())); } function StudioDashboard() { - const { props } = X(); + const { props } = X$1(); const overview = props.overview || {}; const analytics = props.analytics || {}; const kpis = overview.kpis || {}; @@ -96023,12 +100057,12 @@ function StudioDashboard() { ["Comments", analytics.totals?.comments] ].map(([label, value]) => /* @__PURE__ */ React.createElement("div", { key: label, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", null, label), /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, Number(value || 0).toLocaleString()))))))), showWidget("stale_drafts") && /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Stale drafts"), /* @__PURE__ */ React.createElement("a", { href: "/studio/content?bucket=drafts&stale=only&module=stories", className: "text-sm font-medium text-sky-100" }, "Filter stale work")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-4" }, (overview.stale_drafts || []).map((item) => /* @__PURE__ */ React.createElement(ContinueWorkingCard, { key: item.id, item }))))); } -const __vite_glob_0_97 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_102 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioDashboard }, Symbol.toStringTag, { value: "Module" })); function StudioDrafts() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement( StudioContentBrowser, { @@ -96040,7 +100074,7 @@ function StudioDrafts() { } )); } -const __vite_glob_0_98 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_103 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioDrafts }, Symbol.toStringTag, { value: "Module" })); @@ -96069,7 +100103,7 @@ async function requestJson$4(url, method, body2) { return payload; } function StudioFeatured() { - const { props } = X(); + const { props } = X$1(); const [featuredModules, setFeaturedModules] = reactExports.useState(props.featuredModules || []); const [selected, setSelected] = reactExports.useState(props.selected || {}); const [saving, setSaving] = reactExports.useState(false); @@ -96123,7 +100157,7 @@ function StudioFeatured() { })) : /* @__PURE__ */ React.createElement("div", { className: "mt-5 rounded-[24px] border border-dashed border-white/15 px-6 py-10 text-center text-sm text-slate-400" }, "No published ", module.label.toLowerCase(), " candidates yet.")); }))); } -const __vite_glob_0_99 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_104 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioFeatured }, Symbol.toStringTag, { value: "Module" })); @@ -96131,7 +100165,7 @@ function SummaryCard$1({ label, value, icon }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: icon }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(value || 0).toLocaleString())); } function StudioFollowers() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const filters = listing.filters || {}; const summary = listing.summary || {}; @@ -96149,36 +100183,36 @@ function StudioFollowers() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement(SummaryCard$1, { label: "Total followers", value: summary.total_followers, icon: "fa-solid fa-user-group" }), /* @__PURE__ */ React.createElement(SummaryCard$1, { label: "Following back", value: summary.following_back, icon: "fa-solid fa-arrows-rotate" }), /* @__PURE__ */ React.createElement(SummaryCard$1, { label: "Not followed yet", value: summary.not_followed, icon: "fa-solid fa-user-plus" })), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 lg:grid-cols-[minmax(0,1fr)_220px_220px]" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Search"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateQuery({ q: event.target.value, page: 1 }), placeholder: "Search followers", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Sort"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.sort || "recent", onChange: (val) => updateQuery({ sort: val, page: 1 }), options: listing.sort_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, "Relationship"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.relationship || "all", onChange: (val) => updateQuery({ relationship: val, page: 1 }), options: listing.relationship_options || [], searchable: false }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3" }, items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: "flex flex-col gap-4 rounded-[24px] border border-white/10 bg-black/20 p-4 md:flex-row md:items-center md:justify-between" }, /* @__PURE__ */ React.createElement("a", { href: item.profile_url, className: "flex min-w-0 items-center gap-4" }, item.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: item.avatar_url, alt: item.username, className: "h-14 w-14 rounded-[18px] object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-14 w-14 items-center justify-center rounded-[18px] bg-white/5 text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-base font-semibold text-white" }, item.name), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "@", item.username))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-4 text-sm text-slate-400 md:grid-cols-4 md:text-right" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Uploads"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, Number(item.uploads_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Followers"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, Number(item.followers_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Followed"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, item.followed_at ? new Date(item.followed_at).toLocaleDateString() : "—")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Status"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 font-semibold text-white" }, item.is_following_back ? "Following back" : "Not followed")))))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) <= 1, onClick: () => updateQuery({ page: Math.max(1, (meta.current_page || 1) - 1) }), className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-left" }), "Previous"), /* @__PURE__ */ React.createElement("span", null, "Page ", meta.current_page || 1, " of ", meta.last_page || 1), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) >= (meta.last_page || 1), onClick: () => updateQuery({ page: (meta.current_page || 1) + 1 }), className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Next", /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }))))); } -const __vite_glob_0_100 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_105 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioFollowers }, Symbol.toStringTag, { value: "Module" })); function StudioGroupActivity() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.activity) ? props.activity : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("h2", { className: "text-base font-semibold text-white" }, item.headline), item.is_pinned ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Pinned") : null, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, item.visibility)), item.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, item.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-xs text-slate-500" }, item.actor?.name || item.actor?.username || "System", " • ", item.occurred_at ? new Date(item.occurred_at).toLocaleString() : "Recently"), item.subject?.url ? /* @__PURE__ */ React.createElement("a", { href: item.subject.url, className: "mt-3 inline-flex text-sm font-semibold text-sky-200" }, "Open subject") : null), props.pinPattern ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(props.pinPattern.replace("__ITEM__", String(item.id)), { is_pinned: !item.is_pinned }), className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-sm font-semibold text-white" }, item.is_pinned ? "Unpin" : "Pin") : null))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No activity yet."))); } -const __vite_glob_0_101 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_106 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupActivity }, Symbol.toStringTag, { value: "Module" })); function StudioGroupArtworks() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-[28px] border border-sky-300/20 bg-sky-300/10 p-5 text-sky-100" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em]" }, "Group publish flow"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold" }, "Upload into ", props.studioGroup?.name), /* @__PURE__ */ React.createElement("a", { href: props.uploadUrl, className: "mt-4 inline-flex rounded-full border border-sky-200/20 bg-sky-200/10 px-4 py-2 text-sm font-semibold text-sky-50" }, "New group artwork")), /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: [{ key: "artworks", label: "Artwork", icon: "fa-solid fa-cloud-arrow-up", url: props.uploadUrl }], hideModuleFilter: true })); } -const __vite_glob_0_102 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_107 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupArtworks }, Symbol.toStringTag, { value: "Module" })); function StudioGroupAssets() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.listing?.items) ? props.listing.items : []; - const filters = G({ + const filters = G$1({ q: props.listing?.filters?.q || "", category: props.listing?.filters?.category || "all", bucket: props.listing?.filters?.bucket || "all" }); - const form = G({ + const form = G$1({ title: "", description: "", category: props.categoryOptions?.[0]?.value || "misc", @@ -96207,15 +100241,15 @@ function StudioGroupAssets() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, props.storeUrl ? /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-6" }, /* @__PURE__ */ React.createElement("input", { value: form.data.title, onChange: (event) => form.setData("title", event.target.value), placeholder: "Asset title", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none lg:col-span-2" }), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.category, onChange: (val) => form.setData("category", val), options: props.categoryOptions || [], searchable: false }), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.visibility, onChange: (val) => form.setData("visibility", val), options: props.visibilityOptions || [], searchable: false }), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.status, onChange: (val) => form.setData("status", val), options: props.statusOptions || [], searchable: false }), /* @__PURE__ */ React.createElement("input", { type: "file", onChange: (event) => form.setData("file", event.target.files?.[0] || null), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("textarea", { value: form.data.description, onChange: (event) => form.setData("description", event.target.value), placeholder: "What is this asset for?", rows: 3, className: "mt-4 w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: String(form.data.linked_project_id || ""), onChange: (val) => form.setData("linked_project_id", val), placeholder: "No linked project", options: (props.projectOptions || []).map((o) => ({ value: String(o.id), label: o.title })) }), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white" }, /* @__PURE__ */ React.createElement(Checkbox, { checked: form.data.is_featured, onChange: (event) => form.setData("is_featured", event.target.checked), label: "Featured asset" }))), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "mt-4 rounded-full border border-white/10 bg-white/[0.05] px-5 py-2.5 text-sm font-semibold text-white" }, "Upload asset")) : null, /* @__PURE__ */ React.createElement("form", { onSubmit: applyFilters, className: "mt-6 rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Browse library"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Search and filter shared assets by visibility and category.")), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white" }, "Apply filters")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-4 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement("input", { value: filters.data.q, onChange: (event) => filters.setData("q", event.target.value), placeholder: "Search title, description, or filename", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.data.category, onChange: (val) => filters.setData("category", val), options: [{ value: "all", label: "All categories" }, ...props.categoryOptions || []], searchable: false }), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.data.bucket, onChange: (val) => filters.setData("bucket", val), options: [{ value: "all", label: "All visibility levels" }, ...(props.listing?.bucket_options || []).filter((option) => option.value !== "all")], searchable: false }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, items.length > 0 ? items.map((asset) => /* @__PURE__ */ React.createElement("div", { key: asset.id, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, asset.title), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-500" }, asset.category, " • ", asset.visibility, " • ", asset.status)), /* @__PURE__ */ React.createElement("a", { href: asset.download_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-sm font-semibold text-white" }, "Download")), asset.description ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, asset.description) : null, props.updatePattern ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.patch(props.updatePattern.replace("__ASSET__", String(asset.id)), { title: asset.title, description: asset.description || "", category: asset.category, visibility: asset.visibility, status: asset.status === "active" ? "archived" : "active", linked_project_id: asset.linked_project?.id || "", is_featured: asset.is_featured }), className: "mt-4 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, asset.status === "active" ? "Archive" : "Reactivate") : null)) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No assets yet."))); } -const __vite_glob_0_103 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_108 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupAssets }, Symbol.toStringTag, { value: "Module" })); function StudioGroupChallengeEditor() { - const { props } = X(); + const { props } = X$1(); const challenge = props.challenge || null; const outcomeArtworkOptions = Array.isArray(challenge?.artworks) ? challenge.artworks : []; - const form = G({ + const form = G$1({ title: challenge?.title || "", summary: challenge?.summary || "", description: challenge?.description || "", @@ -96240,7 +100274,7 @@ function StudioGroupChallengeEditor() { })) : [], cover_file: null }); - const attachForm = G({ artwork_id: "" }); + const attachForm = G$1({ artwork_id: "" }); const submit = (event) => { event.preventDefault(); const options = { forceFormData: true, preserveScroll: true }; @@ -96279,24 +100313,24 @@ function StudioGroupChallengeEditor() { attachForm.post(props.attachArtworkUrl, { preserveScroll: true }); }, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Attach artwork"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(attachForm.data.artwork_id || ""), onChange: (val) => attachForm.setData("artwork_id", val), placeholder: "Choose artwork", options: (props.artworkOptions || []).map((o) => ({ value: String(o.id), label: o.title })) }), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "mt-4 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white" }, "Attach")) : null))); } -const __vite_glob_0_104 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_109 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupChallengeEditor }, Symbol.toStringTag, { value: "Module" })); function StudioGroupChallenges() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.listing?.items) ? props.listing.items : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "Challenges keep the group active between releases and give members a focused creative prompt."), props.createUrl ? /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Create challenge") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, items.length > 0 ? items.map((challenge) => /* @__PURE__ */ React.createElement("a", { key: challenge.id, href: challenge.urls?.edit || challenge.url, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, challenge.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, challenge.status)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, challenge.summary || "Challenge page"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-xs text-slate-500" }, challenge.entry_count || 0, " linked entries"))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No challenges yet."))); } -const __vite_glob_0_105 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_110 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupChallenges }, Symbol.toStringTag, { value: "Module" })); function StudioGroupCollections() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 rounded-[28px] border border-sky-300/20 bg-sky-300/10 p-5 text-sky-100" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em]" }, "Shared curation"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold" }, "Create collections for ", props.studioGroup?.name), /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "mt-4 inline-flex rounded-full border border-sky-200/20 bg-sky-200/10 px-4 py-2 text-sm font-semibold text-sky-50" }, "New group collection")), /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: [{ key: "collections", label: "Collection", icon: "fa-solid fa-layer-group", url: props.createUrl }], hideModuleFilter: true })); } -const __vite_glob_0_106 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_111 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupCollections }, Symbol.toStringTag, { value: "Module" })); @@ -96317,7 +100351,7 @@ function resolveMediaPreviewUrl$1(path, filesCdnUrl) { return `${String(filesCdnUrl || "").replace(/\/$/, "")}/${trimmed.replace(/^\/+/, "")}`; } function StudioGroupCreate() { - const { props } = X(); + const { props } = X$1(); const filesCdnUrl = props?.cdn?.files_url || ""; const avatarInputRef = reactExports.useRef(null); const bannerInputRef = reactExports.useRef(null); @@ -96410,7 +100444,7 @@ function StudioGroupCreate() { } )), /* @__PURE__ */ React.createElement("section", { className: "mx-auto max-w-3xl rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Name"), /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: handleNameChange, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: handleSlugChange, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Short description"), /* @__PURE__ */ React.createElement("input", { value: form.headline, onChange: (event) => setForm((current) => ({ ...current, headline: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "About"), /* @__PURE__ */ React.createElement("textarea", { value: form.bio, onChange: (event) => setForm((current) => ({ ...current, bio: event.target.value })), rows: 6, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Type / category"), /* @__PURE__ */ React.createElement("input", { value: form.type, onChange: (event) => setForm((current) => ({ ...current, type: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Founded date"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.founded_at, onChange: (nextValue) => setForm((current) => ({ ...current, founded_at: nextValue })), mode: "date", placeholder: "Pick the founding date", clearable: true, className: "bg-black/20" }))), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Website"), /* @__PURE__ */ React.createElement("input", { value: form.website_url, onChange: (event) => setForm((current) => ({ ...current, website_url: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, "Avatar / logo"), /* @__PURE__ */ React.createElement("div", { className: "flex h-28 w-28 items-center justify-center overflow-hidden rounded-[24px] border border-white/10 bg-white/[0.04]" }, resolvedAvatarPreview ? /* @__PURE__ */ React.createElement("img", { src: resolvedAvatarPreview, alt: "Avatar preview", className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-image text-slate-500" })), /* @__PURE__ */ React.createElement("input", { ref: avatarInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFileSelected("avatar_file", setAvatarPreview), className: "hidden" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => avatarInputRef.current?.click(), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Upload avatar"), form.avatar_file ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearSelectedFile("avatar_file", setAvatarPreview, avatarInputRef), className: "rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300" }, "Use URL instead") : null), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Or paste an image URL"), /* @__PURE__ */ React.createElement("input", { value: form.avatar_path, onChange: (event) => setForm((current) => ({ ...current, avatar_path: event.target.value })), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, "Cover image"), /* @__PURE__ */ React.createElement("div", { className: "flex h-28 w-full items-center justify-center overflow-hidden rounded-[24px] border border-white/10 bg-white/[0.04]" }, resolvedBannerPreview ? /* @__PURE__ */ React.createElement("img", { src: resolvedBannerPreview, alt: "Cover preview", className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-panorama text-slate-500" })), /* @__PURE__ */ React.createElement("input", { ref: bannerInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFileSelected("banner_file", setBannerPreview), className: "hidden" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => bannerInputRef.current?.click(), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Upload cover"), form.banner_file ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearSelectedFile("banner_file", setBannerPreview, bannerInputRef), className: "rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300" }, "Use URL instead") : null), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Or paste an image URL"), /* @__PURE__ */ React.createElement("input", { value: form.banner_path, onChange: (event) => setForm((current) => ({ ...current, banner_path: event.target.value })), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Visibility"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.visibility, onChange: (val) => setForm((current) => ({ ...current, visibility: val })), options: props.visibilityOptions || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Membership policy"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.membership_policy, onChange: (val) => setForm((current) => ({ ...current, membership_policy: val })), options: props.membershipPolicyOptions || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-200" }, "Links"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: addLink, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Add link")), form.links_json.map((item, index2) => /* @__PURE__ */ React.createElement("div", { key: `link-${index2}`, className: "grid gap-3 md:grid-cols-[0.8fr_1.2fr_auto]" }, /* @__PURE__ */ React.createElement("input", { value: item.label, onChange: (event) => updateLink(index2, "label", event.target.value), placeholder: "Label", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: item.url, onChange: (event) => updateLink(index2, "url", event.target.value), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeLink(index2), className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Remove")))), /* @__PURE__ */ React.createElement("div", { className: "flex justify-end gap-3" }, /* @__PURE__ */ React.createElement("a", { href: "/studio/groups", className: "rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-semibold text-white" }, "Cancel"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: submit, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100" }, "Create group"))))); } -const __vite_glob_0_107 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_112 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupCreate }, Symbol.toStringTag, { value: "Module" })); @@ -96427,7 +100461,7 @@ function ActivityCard({ item }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.headline), item.is_pinned ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-300/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Pinned") : null), item.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, item.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs text-slate-500" }, item.actor?.name || item.actor?.username || "System", " • ", item.occurred_at ? new Date(item.occurred_at).toLocaleString() : "Recently"), item.subject?.url ? /* @__PURE__ */ React.createElement("a", { href: item.subject.url, className: "mt-3 inline-flex text-sm font-semibold text-sky-200" }, "Open subject") : null); } function StudioGroupDashboard() { - const { props } = X(); + const { props } = X$1(); const group = props.studioGroup; const members = Array.isArray(props.members) ? props.members : []; const dashboard = props.dashboard || {}; @@ -96475,14 +100509,14 @@ function StudioGroupDashboard() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3 xl:grid-cols-6" }, /* @__PURE__ */ React.createElement(StatCard, { label: "Artworks", value: group?.counts?.artworks, icon: "fa-solid fa-images" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Collections", value: group?.counts?.collections, icon: "fa-solid fa-layer-group" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Followers", value: group?.counts?.followers, icon: "fa-solid fa-user-group" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Active members", value: dashboard?.active_members_count || group?.counts?.members, icon: "fa-solid fa-people-group" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Projects", value: dashboard?.projects_count, icon: "fa-solid fa-diagram-project" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Releases", value: dashboard?.published_releases_count || dashboard?.releases_count, icon: "fa-solid fa-rocket" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Assets", value: dashboard?.assets_count, icon: "fa-solid fa-box-archive" })), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Quick actions"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Run the most common group tasks without leaving the dashboard."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, quickActions.map((action) => /* @__PURE__ */ React.createElement("a", { key: action.label, href: action.href, className: `rounded-[24px] border px-4 py-4 transition hover:translate-y-[-1px] hover:border-white/20 ${toneClasses2[action.tone] || toneClasses2.sky}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-current/20 bg-black/10" }, /* @__PURE__ */ React.createElement("i", { className: action.icon })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold" }, action.label), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs opacity-80" }, action.detail)))))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, "Pending action"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Drafts and scheduled items that still need a publishing decision.")), /* @__PURE__ */ React.createElement("div", { className: "text-right text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", null, Number(dashboard?.draft_artworks_count || 0), " drafts"), /* @__PURE__ */ React.createElement("div", null, Number(dashboard?.scheduled_artworks_count || 0), " scheduled"))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, draftsPendingAction.length > 0 ? draftsPendingAction.map((artwork) => /* @__PURE__ */ React.createElement(ContentCard, { key: artwork.id, item: artwork, fallbackLabel: "Draft" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No drafts waiting", description: "This group has no draft artworks waiting for review or completion right now." }))), pendingJoinRequests.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, "Pending join requests"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Applicants waiting for a review decision.")), group?.urls?.studio_join_requests ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_join_requests, className: "text-sm font-semibold text-sky-200" }, "Open queue") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, pendingJoinRequests.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, item.user?.name || item.user?.username), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, item.desired_role_label || item.desired_role || "Contributor", " • ", item.created_at ? new Date(item.created_at).toLocaleDateString() : "New"))))) : null), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Members"), /* @__PURE__ */ React.createElement("a", { href: group?.urls?.studio_members, className: "text-sm font-semibold text-sky-200" }, "Manage")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-2 sm:grid-cols-2" }, Object.entries(roleSummary).map(([role, count]) => /* @__PURE__ */ React.createElement("div", { key: role, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, role), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xl font-semibold text-white" }, Number(count))))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, members.slice(0, 6).map((member) => /* @__PURE__ */ React.createElement("div", { key: member.id, className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, member.user?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: member.user.avatar_url, alt: member.user.name || member.user.username, className: "h-11 w-11 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "truncate font-semibold text-white" }, member.user?.name || member.user?.username), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, member.role))))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Recruitment"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-lg font-semibold text-white" }, recruitment?.is_recruiting ? recruitment.headline || "Recruiting is active" : "Recruitment is off"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, recruitment?.description || "Set open roles, skills, and contact instructions from the recruitment page.")))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Releases"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Track featured drops and current release pipelines.")), group?.urls?.studio_releases ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_releases, className: "text-sm font-semibold text-sky-200" }, "Manage") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentReleases.length > 0 ? recentReleases.map((release) => /* @__PURE__ */ React.createElement(ContentCard, { key: release.id, item: release, fallbackLabel: "Release" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No releases yet", description: "Create a release to track milestones, contributors, and publication status." }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Projects"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Recent structured releases and collaboration hubs.")), group?.urls?.studio_projects ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_projects, className: "text-sm font-semibold text-sky-200" }, "Manage") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentProjects.length > 0 ? recentProjects.map((project) => /* @__PURE__ */ React.createElement(ContentCard, { key: project.id, item: project, fallbackLabel: "Project" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No projects yet", description: "Create a project to bundle shared assets, linked artworks, and a release state." }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Challenges"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Current creative prompts and challenge arcs.")), group?.urls?.studio_challenges ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_challenges, className: "text-sm font-semibold text-sky-200" }, "Manage") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentChallenges.length > 0 ? recentChallenges.map((challenge) => /* @__PURE__ */ React.createElement(ContentCard, { key: challenge.id, item: challenge, fallbackLabel: "Challenge" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No challenges yet", description: "Launch a challenge to keep the group active between major releases." })))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Trust summary"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Public-facing trust labels and internal contributor health snapshot.")), group?.urls?.studio_reputation ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_reputation, className: "text-sm font-semibold text-sky-200" }, "Open dashboard") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, trustSignals.map((signal) => /* @__PURE__ */ React.createElement("span", { key: signal.key, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-white" }, signal.label))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Contributors"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold text-white" }, Number(reputationSummary?.counts?.contributors || 0))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Group badges"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold text-white" }, Number(reputationSummary?.counts?.group_badges || 0))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Contributor highlights"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Recent high-trust contributors and badge unlocks."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, Array.isArray(reputationSummary?.top_contributors) && reputationSummary.top_contributors.length > 0 ? reputationSummary.top_contributors.slice(0, 4).map((entry) => /* @__PURE__ */ React.createElement("div", { key: entry.user?.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, entry.user?.name || entry.user?.username), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, entry.summary || "Contributor"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs text-slate-500" }, entry.counts?.releases || 0, " releases • ", entry.counts?.credited_artworks || 0, " artworks"))) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No contributor signals yet", description: "Release and milestone activity will populate contributor reputation here." })))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent artworks"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Latest published work released under this group identity.")), /* @__PURE__ */ React.createElement("a", { href: group?.urls?.studio_artworks, className: "text-sm font-semibold text-sky-200" }, "View all")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentArtworks.length > 0 ? recentArtworks.map((artwork) => /* @__PURE__ */ React.createElement(ContentCard, { key: artwork.id, item: artwork, fallbackLabel: "Published" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No published artworks yet", description: "Publish the first group artwork to start building this feed." }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Events"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Upcoming or recently updated moments on the group timeline.")), group?.urls?.studio_events ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_events, className: "text-sm font-semibold text-sky-200" }, "Manage") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentEvents.length > 0 ? recentEvents.map((event) => /* @__PURE__ */ React.createElement(ContentCard, { key: event.id, item: event, fallbackLabel: "Event" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No events yet", description: "Schedule a launch, stream, or milestone to start the group timeline." })))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent collections"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Collections most recently updated in this group workspace.")), /* @__PURE__ */ React.createElement("a", { href: group?.urls?.studio_collections, className: "text-sm font-semibold text-sky-200" }, "View all")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentCollections.length > 0 ? recentCollections.map((collection) => /* @__PURE__ */ React.createElement(ContentCard, { key: collection.id, item: collection, fallbackLabel: "Collection" })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No collections yet", description: "Create a collection to organize group work into campaigns, series, or themed sets." }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Activity feed"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Pinned and recent internal or public timeline items.")), group?.urls?.studio_activity ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_activity, className: "text-sm font-semibold text-sky-200" }, "Open feed") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, recentActivity.length > 0 ? recentActivity.map((item) => /* @__PURE__ */ React.createElement(ActivityCard, { key: item.id, item })) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No activity items yet", description: "Publishing projects, events, posts, and member milestones will populate this feed." })))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Review queue"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Latest artwork submissions waiting for moderation.")), group?.urls?.studio_review ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_review, className: "text-sm font-semibold text-sky-200" }, "Open queue") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, reviewQueuePreview.length > 0 ? reviewQueuePreview.map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.urls?.edit, className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-500" }, item.group_review_status))) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No pending reviews", description: "Contributor submissions will appear here when they are sent for review." }))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent posts"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Announcements and updates published from the group.")), group?.urls?.studio_posts ? /* @__PURE__ */ React.createElement("a", { href: group.urls.studio_posts, className: "text-sm font-semibold text-sky-200" }, "Manage posts") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, recentPosts.length > 0 ? recentPosts.map((post2) => /* @__PURE__ */ React.createElement("a", { key: post2.id, href: post2.url, className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, post2.type), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-base font-semibold text-white" }, post2.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, post2.excerpt || "Open post"))) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No posts yet", description: "Create the first group announcement to add a public news feed." })))), /* @__PURE__ */ React.createElement("section", { className: "mt-6 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent history"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3" }, recentHistory.length > 0 ? recentHistory.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.summary || item.action_type), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs text-slate-400" }, item.actor?.name || item.actor?.username || "System", " • ", item.created_at ? new Date(item.created_at).toLocaleString() : "Recently"))) : /* @__PURE__ */ React.createElement(EmptyCard, { title: "No history yet", description: "Audit events will appear here as members review requests, posts, and submissions." })))); } -const __vite_glob_0_108 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_113 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupDashboard }, Symbol.toStringTag, { value: "Module" })); function StudioGroupEventEditor() { - const { props } = X(); + const { props } = X$1(); const eventRecord = props.event || null; - const form = G({ + const form = G$1({ title: eventRecord?.title || "", summary: eventRecord?.summary || "", description: eventRecord?.description || "", @@ -96514,16 +100548,16 @@ function StudioGroupEventEditor() { form.post(props.publishUrl, { preserveScroll: true }); }, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rounded-full border border-white/10 bg-white/[0.05] px-5 py-2.5 text-sm font-semibold text-white" }, "Publish event")) : null)); } -const __vite_glob_0_109 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_114 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupEventEditor }, Symbol.toStringTag, { value: "Module" })); function StudioGroupEvents() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.listing?.items) ? props.listing.items : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "Events let the group announce launches, sessions, milestones, and time-based updates."), props.createUrl ? /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Create event") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, items.length > 0 ? items.map((event) => /* @__PURE__ */ React.createElement("a", { key: event.id, href: event.urls?.edit || event.url, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, event.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, event.status)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, event.summary || "Event page"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-xs text-slate-500" }, event.start_at ? new Date(event.start_at).toLocaleString() : "Unscheduled", " • ", event.event_type))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No events yet."))); } -const __vite_glob_0_110 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_115 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupEvents }, Symbol.toStringTag, { value: "Module" })); @@ -96536,7 +100570,7 @@ function formatInviteTimestamp(value) { } } function StudioGroupInvitations() { - const { props } = X(); + const { props } = X$1(); const invitations = Array.isArray(props.invitations) ? props.invitations : []; const activeMembers = Array.isArray(props.members) ? props.members.filter((member) => member.status === "active") : []; const [invite, setInvite] = reactExports.useState({ username: "", role: "contributor", note: "", expires_in_days: 7 }); @@ -96550,7 +100584,7 @@ function StudioGroupInvitations() { ); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("section", { className: "mb-6 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/75" }, "Group invitations"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold text-white" }, "Invite collaborators into ", props.studioGroup?.name), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-6 text-slate-300" }, "Pending invites stay separate from active members here, so owners and admins can review who was invited, when the invite expires, and revoke access before acceptance.")), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement(xe, { href: props.studioGroup?.urls?.studio_members, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Members"), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100" }, pendingInvites.length, " pending"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-[1.1fr_0.8fr_1fr_0.7fr_auto]" }, /* @__PURE__ */ React.createElement("input", { value: invite.username, onChange: (event) => setInvite((current) => ({ ...current, username: event.target.value })), placeholder: "Username", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement(NovaSelect, { value: invite.role, onChange: (val) => setInvite((current) => ({ ...current, role: val })), searchable: false, options: [{ value: "contributor", label: "Contributor" }, { value: "editor", label: "Editor" }, { value: "admin", label: "Admin" }] }), /* @__PURE__ */ React.createElement("input", { value: invite.note, onChange: (event) => setInvite((current) => ({ ...current, note: event.target.value })), placeholder: "Optional note", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: invite.expires_in_days, onChange: (event) => setInvite((current) => ({ ...current, expires_in_days: event.target.value })), type: "number", min: "1", max: "30", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(props.endpoints?.invite, { ...invite, expires_in_days: Number(invite.expires_in_days || 7) || 7 }), className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100" }, "Send invite"))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Pending invitations"), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, pendingInvites.length, " outstanding")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, pendingInvites.length > 0 ? pendingInvites.map((inviteRow) => /* @__PURE__ */ React.createElement("article", { key: inviteRow.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 md:flex-row md:items-center" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, inviteRow.user?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: inviteRow.user.avatar_url, alt: inviteRow.user.name || inviteRow.user.username, className: "h-12 w-12 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, inviteRow.user?.name || inviteRow.user?.username), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, inviteRow.role_label || inviteRow.role))), /* @__PURE__ */ React.createElement("div", { className: "md:ml-auto flex flex-wrap items-center gap-3 text-xs text-slate-400" }, inviteRow.invited_by ? /* @__PURE__ */ React.createElement("span", null, "Invited by ", inviteRow.invited_by.name || inviteRow.invited_by.username) : null, inviteRow.invited_at ? /* @__PURE__ */ React.createElement("span", null, "Sent ", formatInviteTimestamp(inviteRow.invited_at)) : null, inviteRow.expires_at ? /* @__PURE__ */ React.createElement("span", null, "Expires ", formatInviteTimestamp(inviteRow.expires_at)) : null)), inviteRow.note ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, inviteRow.note) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, inviteRow.can_revoke && inviteRow.revoke_url ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.delete(inviteRow.revoke_url), className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-2 text-sm font-semibold text-rose-100" }, "Revoke invite") : null))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 px-6 py-12 text-center text-slate-400" }, "No pending invites for this group."))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Recent invite history"), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, revokedInvites.length, " revoked or expired")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, revokedInvites.length > 0 ? revokedInvites.map((inviteRow) => /* @__PURE__ */ React.createElement("article", { key: inviteRow.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, inviteRow.user?.name || inviteRow.user?.username), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-400" }, inviteRow.is_expired ? "Expired" : "Revoked", " • ", inviteRow.role_label || inviteRow.role), inviteRow.invited_at ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, "Originally sent ", formatInviteTimestamp(inviteRow.invited_at)) : null)) : /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/10 px-4 py-8 text-center text-slate-400" }, "No recent invite history yet."))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Active members"), /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-400" }, activeMembers.length, " active")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, activeMembers.slice(0, 6).map((member) => /* @__PURE__ */ React.createElement("div", { key: member.id, className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, member.user?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: member.user.avatar_url, alt: member.user.name || member.user.username, className: "h-11 w-11 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "truncate font-semibold text-white" }, member.user?.name || member.user?.username), /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-[0.16em] text-slate-400" }, member.role_label || member.role))))))))); } -const __vite_glob_0_111 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_116 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupInvitations }, Symbol.toStringTag, { value: "Module" })); @@ -96561,7 +100595,7 @@ function HistoryList({ items }) { return /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, items.map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.summary || item.action_type), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-400" }, item.actor?.name || item.actor?.username || "System", " • ", item.created_at ? new Date(item.created_at).toLocaleString() : "Recently")))); } function StudioGroupJoinRequests() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const items = Array.isArray(listing.items) ? listing.items : []; const approve = (request) => { @@ -96579,7 +100613,7 @@ function routeUrl(baseUrl, id, action) { if (!baseUrl) return ""; return `${String(baseUrl).replace(/\/$/, "")}/${id}/${action}`; } -const __vite_glob_0_112 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_117 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupJoinRequests }, Symbol.toStringTag, { value: "Module" })); @@ -96595,7 +100629,7 @@ function prettifyPermission(value) { return String(value || "").replaceAll("_", " "); } function StudioGroupMembers() { - const { props } = X(); + const { props } = X$1(); const canManageMembers = Boolean(props.canManageMembers); const [invite, setInvite] = reactExports.useState({ username: "", role: "contributor", note: "" }); const [search2, setSearch] = reactExports.useState(""); @@ -96646,14 +100680,14 @@ function StudioGroupMembers() { return /* @__PURE__ */ React.createElement("div", { key: option.value, className: "rounded-2xl border border-white/10 bg-white/[0.03] p-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, option.label), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setPermissionState(member.id, option.value, "inherit"), className: `rounded-full border px-3 py-1.5 text-xs font-semibold ${current === "inherit" ? "border-white/20 bg-white/[0.08] text-white" : "border-white/10 bg-transparent text-slate-300"}` }, "Inherit"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setPermissionState(member.id, option.value, "allow"), className: `rounded-full border px-3 py-1.5 text-xs font-semibold ${current === "allow" ? "border-emerald-300/20 bg-emerald-400/10 text-emerald-100" : "border-white/10 bg-transparent text-slate-300"}` }, "Allow"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setPermissionState(member.id, option.value, "deny"), className: `rounded-full border px-3 py-1.5 text-xs font-semibold ${current === "deny" ? "border-rose-300/20 bg-rose-400/10 text-rose-100" : "border-white/10 bg-transparent text-slate-300"}` }, "Deny"))); }))) : null)), filteredMembers.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "px-4 py-8 text-sm text-slate-400" }, "No members match the current search.") : null)))); } -const __vite_glob_0_113 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_118 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupMembers }, Symbol.toStringTag, { value: "Module" })); function StudioGroupPostEditor() { - const { props } = X(); + const { props } = X$1(); const post2 = props.post || {}; - const form = G({ + const form = G$1({ type: post2.type || "announcement", title: post2.title || "", excerpt: post2.excerpt || "", @@ -96670,23 +100704,23 @@ function StudioGroupPostEditor() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("form", { onSubmit: submit, className: "grid gap-6 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.type, onChange: (val) => form.setData("type", val), options: Array.isArray(props.typeOptions) ? props.typeOptions : [], searchable: false })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Title"), /* @__PURE__ */ React.createElement("input", { value: form.data.title, onChange: (event) => form.setData("title", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Excerpt"), /* @__PURE__ */ React.createElement("textarea", { value: form.data.excerpt, onChange: (event) => form.setData("excerpt", event.target.value), rows: 3, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Content"), /* @__PURE__ */ React.createElement("textarea", { value: form.data.content, onChange: (event) => form.setData("content", event.target.value), rows: 12, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Post controls"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 space-y-3" }, /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "w-full rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-3 text-sm font-semibold text-sky-100 disabled:opacity-60" }, "Save"), props.publishUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(props.publishUrl), className: "w-full rounded-full border border-emerald-300/20 bg-emerald-400/10 px-4 py-3 text-sm font-semibold text-emerald-100" }, "Publish") : null, props.pinUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(props.pinUrl), className: "w-full rounded-full border border-amber-300/20 bg-amber-400/10 px-4 py-3 text-sm font-semibold text-amber-100" }, "Toggle pinned") : null, props.archiveUrl ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(props.archiveUrl), className: "w-full rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm font-semibold text-rose-100" }, "Archive") : null)))); } -const __vite_glob_0_114 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_119 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupPostEditor }, Symbol.toStringTag, { value: "Module" })); function StudioGroupPosts() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.listing?.items) ? props.listing.items : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Post library"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Draft, publish, pin, and archive public group posts.")), props.createUrl ? /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100" }, "New post") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 md:grid-cols-2" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, item.type), /* @__PURE__ */ React.createElement("h3", { className: "mt-2 text-lg font-semibold text-white" }, item.title)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-end gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-slate-300" }, item.status), item.is_pinned ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-amber-100" }, "Pinned") : null)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-300" }, item.excerpt || item.content || "No excerpt yet."), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: item.urls?.edit, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Edit"), item.urls?.public ? /* @__PURE__ */ React.createElement("a", { href: item.urls.public, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "View") : null))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-5 text-sm text-slate-400" }, "No posts yet.")))); } -const __vite_glob_0_115 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_120 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupPosts }, Symbol.toStringTag, { value: "Module" })); function StudioGroupProjectEditor() { - const { props } = X(); + const { props } = X$1(); const project = props.project || null; - const form = G({ + const form = G$1({ title: project?.title || "", summary: project?.summary || "", description: project?.description || "", @@ -96701,10 +100735,10 @@ function StudioGroupProjectEditor() { member_user_ids: Array.isArray(project?.team) ? project.team.map((member) => member.id) : [], cover_file: null }); - const artworkAttach = G({ artwork_id: "" }); - const assetAttach = G({ asset_id: "" }); - const statusForm = G({ status: project?.status || props.statusOptions?.[0]?.value || "planned" }); - const milestoneForm = G({ title: "", summary: "", status: "pending", due_date: "", owner_user_id: "", notes: "" }); + const artworkAttach = G$1({ artwork_id: "" }); + const assetAttach = G$1({ asset_id: "" }); + const statusForm = G$1({ status: project?.status || props.statusOptions?.[0]?.value || "planned" }); + const milestoneForm = G$1({ title: "", summary: "", status: "pending", due_date: "", owner_user_id: "", notes: "" }); const submit = (event) => { event.preventDefault(); const options = { forceFormData: true, preserveScroll: true }; @@ -96728,17 +100762,17 @@ function StudioGroupProjectEditor() { milestoneForm.post(props.storeMilestoneUrl, { preserveScroll: true, onSuccess: () => milestoneForm.reset("title", "summary", "due_date", "owner_user_id", "notes") }); }, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Milestones"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, /* @__PURE__ */ React.createElement("input", { value: milestoneForm.data.title, onChange: (event) => milestoneForm.setData("title", event.target.value), placeholder: "Milestone title", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("textarea", { value: milestoneForm.data.summary, onChange: (event) => milestoneForm.setData("summary", event.target.value), placeholder: "Summary", rows: 3, className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: milestoneForm.data.status, onChange: (val) => milestoneForm.setData("status", val), searchable: false, options: ["pending", "active", "blocked", "completed", "cancelled"].map((s2) => ({ value: s2, label: s2 })) }), /* @__PURE__ */ React.createElement(DateTimePicker, { value: milestoneForm.data.due_date, onChange: (nextValue) => milestoneForm.setData("due_date", nextValue), mode: "date", placeholder: "Due date", clearable: true, className: "bg-black/20" })), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(milestoneForm.data.owner_user_id || ""), onChange: (val) => milestoneForm.setData("owner_user_id", val), placeholder: "No owner", options: (props.memberOptions || []).map((o) => ({ value: String(o.id), label: o.name || o.username })) }), /* @__PURE__ */ React.createElement("textarea", { value: milestoneForm.data.notes, onChange: (event) => milestoneForm.setData("notes", event.target.value), placeholder: "Notes", rows: 3, className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white" }, "Add milestone")), Array.isArray(project?.milestones) && project.milestones.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3" }, project.milestones.map((milestone) => /* @__PURE__ */ React.createElement("div", { key: milestone.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, milestone.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, milestone.owner?.name || milestone.owner?.username || "No owner", milestone.due_date ? ` • due ${milestone.due_date}` : "")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.patch(props.updateMilestonePattern.replace("__MILESTONE__", String(milestone.id)), { title: milestone.title, summary: milestone.summary || "", status: milestone.status === "completed" ? "active" : "completed", due_date: milestone.due_date || "", owner_user_id: milestone.owner?.id || "", notes: milestone.notes || "" }, { preserveScroll: true }), className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold text-white" }, "Mark ", milestone.status === "completed" ? "active" : "complete")), milestone.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, milestone.summary) : null))) : null) : null))); } -const __vite_glob_0_116 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_121 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupProjectEditor }, Symbol.toStringTag, { value: "Module" })); function StudioGroupProjects() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const items = Array.isArray(listing.items) ? listing.items : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "Projects give the group a structured place for releases, teams, and linked outputs."), props.createUrl ? /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Create project") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, items.length > 0 ? items.map((project) => /* @__PURE__ */ React.createElement("a", { key: project.id, href: project.urls?.edit || project.url, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, project.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, project.status)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, project.summary || "Project page"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-xs text-slate-500" }, project.counts?.artworks || 0, " artworks • ", project.counts?.assets || 0, " assets • ", project.counts?.team || 0, " team • ", project.counts?.milestones || 0, " milestones • ", project.counts?.releases || 0, " releases"))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No projects yet."))); } -const __vite_glob_0_117 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_122 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupProjects }, Symbol.toStringTag, { value: "Module" })); @@ -96746,9 +100780,9 @@ function toggleItem(list2, value) { return list2.includes(value) ? list2.filter((item) => item !== value) : [...list2, value]; } function StudioGroupRecruitment() { - const { props } = X(); + const { props } = X$1(); const recruitment = props.recruitment || {}; - const form = G({ + const form = G$1({ is_recruiting: Boolean(recruitment.is_recruiting), headline: recruitment.headline || "", description: recruitment.description || "", @@ -96769,7 +100803,7 @@ function StudioGroupRecruitment() { return /* @__PURE__ */ React.createElement("button", { key: option.value, type: "button", onClick: () => form.setData("skills_json", toggleItem(form.data.skills_json, option.value)), className: `rounded-full border px-3 py-1.5 text-xs font-semibold ${selected ? "border-sky-300/20 bg-sky-300/10 text-sky-100" : "border-white/10 bg-white/[0.03] text-slate-300"}` }, option.label); }))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Application settings"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Contact mode"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.contact_mode, onChange: (val) => form.setData("contact_mode", val), options: Array.isArray(props.contactModes) ? props.contactModes : [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Visibility"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.data.visibility, onChange: (val) => form.setData("visibility", val), options: Array.isArray(props.visibilityOptions) ? props.visibilityOptions : [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("p", { className: "font-semibold text-white" }, "Public preview"), /* @__PURE__ */ React.createElement("p", { className: "mt-2" }, form.data.headline || "No headline yet."), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-slate-400" }, form.data.description || "Recruitment copy will show here once you add it."), form.data.roles_json.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, form.data.roles_json.map((role) => /* @__PURE__ */ React.createElement("span", { key: role, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-white" }, role))) : null), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: form.processing, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-3 text-sm font-semibold text-sky-100 disabled:opacity-60" }, "Save recruitment profile"))))); } -const __vite_glob_0_118 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_123 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupRecruitment }, Symbol.toStringTag, { value: "Module" })); @@ -96777,9 +100811,9 @@ function toDateTimeInput(value) { return value ? String(value).slice(0, 16) : ""; } function StudioGroupReleaseEditor() { - const { props } = X(); + const { props } = X$1(); const release = props.release || null; - const form = G({ + const form = G$1({ title: release?.title || "", summary: release?.summary || "", description: release?.description || "", @@ -96795,10 +100829,10 @@ function StudioGroupReleaseEditor() { is_featured: Boolean(release?.is_featured), cover_file: null }); - const stageForm = G({ current_stage: release?.current_stage || props.stageOptions?.[0]?.value || "concept" }); - const artworkAttach = G({ artwork_id: "" }); - const contributorForm = G({ user_id: "", role_label: "" }); - const milestoneForm = G({ title: "", summary: "", status: "pending", due_date: "", owner_user_id: "", notes: "" }); + const stageForm = G$1({ current_stage: release?.current_stage || props.stageOptions?.[0]?.value || "concept" }); + const artworkAttach = G$1({ artwork_id: "" }); + const contributorForm = G$1({ user_id: "", role_label: "" }); + const milestoneForm = G$1({ title: "", summary: "", status: "pending", due_date: "", owner_user_id: "", notes: "" }); const submit = (event) => { event.preventDefault(); const options = { forceFormData: true, preserveScroll: true }; @@ -96822,19 +100856,19 @@ function StudioGroupReleaseEditor() { milestoneForm.post(props.storeMilestoneUrl, { preserveScroll: true, onSuccess: () => milestoneForm.reset("title", "summary", "due_date", "owner_user_id", "notes") }); }, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Milestones"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, /* @__PURE__ */ React.createElement("input", { value: milestoneForm.data.title, onChange: (event) => milestoneForm.setData("title", event.target.value), placeholder: "Milestone title", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("textarea", { value: milestoneForm.data.summary, onChange: (event) => milestoneForm.setData("summary", event.target.value), placeholder: "Summary", rows: 3, className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: milestoneForm.data.status, onChange: (val) => milestoneForm.setData("status", val), searchable: false, options: ["pending", "active", "blocked", "completed", "cancelled"].map((s2) => ({ value: s2, label: s2 })) }), /* @__PURE__ */ React.createElement(DateTimePicker, { value: milestoneForm.data.due_date, onChange: (nextValue) => milestoneForm.setData("due_date", nextValue), mode: "date", placeholder: "Due date", clearable: true, className: "bg-black/20" })), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(milestoneForm.data.owner_user_id || ""), onChange: (val) => milestoneForm.setData("owner_user_id", val), placeholder: "No owner", options: (props.memberOptions || []).map((o) => ({ value: String(o.id), label: o.name || o.username })) }), /* @__PURE__ */ React.createElement("textarea", { value: milestoneForm.data.notes, onChange: (event) => milestoneForm.setData("notes", event.target.value), placeholder: "Notes", rows: 3, className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white" }, "Add milestone")), Array.isArray(release?.milestones) && release.milestones.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-6 space-y-3" }, release.milestones.map((milestone) => /* @__PURE__ */ React.createElement("div", { key: milestone.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, milestone.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, milestone.owner?.name || milestone.owner?.username || "No owner", milestone.due_date ? ` • due ${milestone.due_date}` : "")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.patch(props.updateMilestonePattern.replace("__MILESTONE__", String(milestone.id)), { title: milestone.title, summary: milestone.summary || "", status: milestone.status === "completed" ? "active" : "completed", due_date: milestone.due_date || "", owner_user_id: milestone.owner?.id || "", notes: milestone.notes || "" }, { preserveScroll: true }), className: "rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-semibold text-white" }, "Mark ", milestone.status === "completed" ? "active" : "complete")), milestone.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, milestone.summary) : null))) : null) : null))); } -const __vite_glob_0_119 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_124 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupReleaseEditor }, Symbol.toStringTag, { value: "Module" })); function StudioGroupReleases() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const items = Array.isArray(listing.items) ? listing.items : []; const bucketOptions = Array.isArray(listing.bucket_options) ? listing.bucket_options : []; const currentBucket = listing.filters?.bucket || "all"; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "Track the release pipeline from draft through public launch, with milestones and contributor credits."), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement(NovaSelect, { value: currentBucket, onChange: (val) => At.get(window.location.pathname, { bucket: val }, { preserveScroll: true, preserveState: true }), options: bucketOptions, searchable: false }), props.createUrl ? /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Create release") : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 lg:grid-cols-2" }, items.length > 0 ? items.map((release) => /* @__PURE__ */ React.createElement("div", { key: release.id, className: "overflow-hidden rounded-[24px] border border-white/10 bg-white/[0.03]" }, release.cover_url ? /* @__PURE__ */ React.createElement("img", { src: release.cover_url, alt: release.title, className: "aspect-[4/3] w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex aspect-[4/3] items-center justify-center bg-white/[0.03] text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-rocket text-2xl" })), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, release.status), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, release.current_stage), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, release.visibility)), /* @__PURE__ */ React.createElement("h2", { className: "mt-3 text-xl font-semibold text-white" }, release.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, release.summary || "Release page"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-xs text-slate-500" }, release.counts?.artworks || 0, " artworks • ", release.counts?.contributors || 0, " contributors • ", release.counts?.milestones || 0, " milestones"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: release.urls?.edit || release.url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Manage"), release.urls?.public ? /* @__PURE__ */ React.createElement("a", { href: release.urls.public, className: "rounded-full border border-white/10 bg-black/20 px-4 py-2 text-sm font-semibold text-white" }, "View public") : null)))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No releases yet."))); } -const __vite_glob_0_120 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_125 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupReleases }, Symbol.toStringTag, { value: "Module" })); @@ -96842,7 +100876,7 @@ function MetricCard({ label, value }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold text-white" }, Number(value || 0).toFixed(1))); } function StudioGroupReputation() { - const { props } = X(); + const { props } = X$1(); const reputation = props.reputation || {}; const trustSignals = Array.isArray(props.trustSignals) ? props.trustSignals : []; const metrics = props.metrics || {}; @@ -96851,7 +100885,7 @@ function StudioGroupReputation() { const memberBadgeUnlocks = Array.isArray(reputation.member_badge_unlocks) ? reputation.member_badge_unlocks : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-5" }, /* @__PURE__ */ React.createElement(MetricCard, { label: "Freshness", value: metrics.freshness_score }), /* @__PURE__ */ React.createElement(MetricCard, { label: "Activity", value: metrics.activity_score }), /* @__PURE__ */ React.createElement(MetricCard, { label: "Release", value: metrics.release_score }), /* @__PURE__ */ React.createElement(MetricCard, { label: "Trust", value: metrics.trust_score }), /* @__PURE__ */ React.createElement(MetricCard, { label: "Collaboration", value: metrics.collaboration_score })), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Trust signals"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Public-safe labels that shape discovery and confidence."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, trustSignals.map((signal) => /* @__PURE__ */ React.createElement("span", { key: signal.key, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-white" }, signal.label))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Contributors"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold text-white" }, Number(reputation.counts?.contributors || 0))), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Member badges"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-2xl font-semibold text-white" }, Number(reputation.counts?.member_badges || 0)))), metrics.last_calculated_at ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-xs text-slate-500" }, "Last calculated ", new Date(metrics.last_calculated_at).toLocaleString()) : null), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Top contributors"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Reputation summaries derived from visible collaboration history."))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, topContributors.length > 0 ? topContributors.map((entry) => /* @__PURE__ */ React.createElement("div", { key: entry.user?.id, className: "rounded-[24px] border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, entry.user?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: entry.user.avatar_url, alt: entry.user?.name || entry.user?.username, className: "h-11 w-11 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "truncate font-semibold text-white" }, entry.user?.name || entry.user?.username), entry.trusted_indicator ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100" }, "Trusted") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, entry.summary || "Contributor"))), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-xs text-slate-500" }, entry.counts?.releases || 0, " releases • ", entry.counts?.projects || 0, " projects • ", entry.counts?.credited_artworks || 0, " artworks • ", entry.counts?.review_actions || 0, " reviews"), Array.isArray(entry.badges) && entry.badges.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, entry.badges.map((badge) => /* @__PURE__ */ React.createElement("span", { key: `${entry.user?.id}-${badge.key}`, className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, badge.label))) : null)) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No contributor reputation signals yet.")))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-6 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Group badges"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, recentBadges.length > 0 ? recentBadges.map((badge) => /* @__PURE__ */ React.createElement("div", { key: badge.key, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, badge.label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, badge.reason))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No group badges awarded yet."))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent member badge unlocks"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, memberBadgeUnlocks.length > 0 ? memberBadgeUnlocks.map((entry) => /* @__PURE__ */ React.createElement("div", { key: `${entry.user?.id}-${entry.badge?.key}`, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, entry.user?.name || entry.user?.username), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-sky-200" }, entry.badge?.label), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, entry.badge?.reason))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No member badge unlocks yet."))))); } -const __vite_glob_0_121 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_126 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupReputation }, Symbol.toStringTag, { value: "Module" })); @@ -96859,7 +100893,7 @@ function actionUrl(item, key) { return item?.urls?.[key] || ""; } function StudioGroupReviewQueue() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const items = Array.isArray(listing.items) ? listing.items : []; const sendAction = (item, action) => { @@ -96868,7 +100902,7 @@ function StudioGroupReviewQueue() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Submission queue"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Review artwork drafts before they publish under the group identity.")), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-slate-300" }, listing.filters?.bucket || "submitted")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-4" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-start gap-4" }, item.thumb ? /* @__PURE__ */ React.createElement("img", { src: item.thumb, alt: item.title, className: "h-24 w-24 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-24 w-24 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-image" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-slate-300" }, item.group_review_status)), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap gap-3 text-xs text-slate-400" }, item.primary_author ? /* @__PURE__ */ React.createElement("span", null, "Author: ", item.primary_author.name || item.primary_author.username) : null, item.uploader ? /* @__PURE__ */ React.createElement("span", null, "Uploader: ", item.uploader.name || item.uploader.username) : null, item.submitted_at ? /* @__PURE__ */ React.createElement("span", null, "Submitted ", new Date(item.submitted_at).toLocaleString()) : null), item.group_review_notes ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 rounded-2xl border border-white/10 bg-white/[0.03] px-3 py-2 text-sm text-slate-300" }, item.group_review_notes) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: item.urls?.edit, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Open draft"), item.can_review ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => sendAction(item, "approve"), className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100" }, "Approve") : null, item.can_review ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => sendAction(item, "needs_changes"), className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-4 py-2 text-sm font-semibold text-amber-100" }, "Needs changes") : null, item.can_review ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => sendAction(item, "reject"), className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Reject") : null))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-5 text-sm text-slate-400" }, "No submissions in this bucket."))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xl font-semibold text-white" }, "Recent history"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (Array.isArray(props.recentHistory) ? props.recentHistory : []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.id, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.summary || item.action_type), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-400" }, item.actor?.name || item.actor?.username || "System", " • ", item.created_at ? new Date(item.created_at).toLocaleString() : "Recently"))))))); } -const __vite_glob_0_122 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_127 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupReviewQueue }, Symbol.toStringTag, { value: "Module" })); @@ -96883,7 +100917,7 @@ function resolveMediaPreviewUrl(path, filesCdnUrl) { return `${String(filesCdnUrl || "").replace(/\/$/, "")}/${trimmed.replace(/^\/+/, "")}`; } function StudioGroupSettings() { - const { props } = X(); + const { props } = X$1(); const group = props.studioGroup || {}; const filesCdnUrl = props?.cdn?.files_url || ""; const featuredArtworkOptions = Array.isArray(props.featuredArtworkOptions) ? props.featuredArtworkOptions : []; @@ -96961,7 +100995,7 @@ function StudioGroupSettings() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("section", { className: "mx-auto max-w-3xl rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-5" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Name"), /* @__PURE__ */ React.createElement("input", { value: form.name, onChange: (event) => setForm((current) => ({ ...current, name: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Slug"), /* @__PURE__ */ React.createElement("input", { value: form.slug, onChange: (event) => setForm((current) => ({ ...current, slug: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Short description"), /* @__PURE__ */ React.createElement("input", { value: form.headline, onChange: (event) => setForm((current) => ({ ...current, headline: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "About"), /* @__PURE__ */ React.createElement("textarea", { value: form.bio, onChange: (event) => setForm((current) => ({ ...current, bio: event.target.value })), rows: 6, className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Type / category"), /* @__PURE__ */ React.createElement("input", { value: form.type, onChange: (event) => setForm((current) => ({ ...current, type: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Founded date"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: form.founded_at, onChange: (nextValue) => setForm((current) => ({ ...current, founded_at: nextValue })), mode: "date", placeholder: "Pick the founding date", clearable: true, className: "bg-black/20" }))), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Website"), /* @__PURE__ */ React.createElement("input", { value: form.website_url, onChange: (event) => setForm((current) => ({ ...current, website_url: event.target.value })), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-5 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, "Avatar / logo"), /* @__PURE__ */ React.createElement("div", { className: "flex h-28 w-28 items-center justify-center overflow-hidden rounded-[24px] border border-white/10 bg-white/[0.04]" }, resolvedAvatarPreview ? /* @__PURE__ */ React.createElement("img", { src: resolvedAvatarPreview, alt: "Avatar preview", className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-image text-slate-500" })), /* @__PURE__ */ React.createElement("input", { ref: avatarInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFileSelected("avatar_file", setAvatarPreview), className: "hidden" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => avatarInputRef.current?.click(), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Upload avatar"), form.avatar_file ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearSelectedFile("avatar_file", setAvatarPreview, avatarInputRef), className: "rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300" }, "Use current path") : null), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Or paste an image URL"), /* @__PURE__ */ React.createElement("input", { value: form.avatar_path, onChange: (event) => setForm((current) => ({ ...current, avatar_path: event.target.value })), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, "Cover image"), /* @__PURE__ */ React.createElement("div", { className: "flex h-28 w-full items-center justify-center overflow-hidden rounded-[24px] border border-white/10 bg-white/[0.04]" }, resolvedBannerPreview ? /* @__PURE__ */ React.createElement("img", { src: resolvedBannerPreview, alt: "Cover preview", className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-panorama text-slate-500" })), /* @__PURE__ */ React.createElement("input", { ref: bannerInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFileSelected("banner_file", setBannerPreview), className: "hidden" }), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => bannerInputRef.current?.click(), className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, "Upload cover"), form.banner_file ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearSelectedFile("banner_file", setBannerPreview, bannerInputRef), className: "rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300" }, "Use current path") : null), /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Or paste an image URL"), /* @__PURE__ */ React.createElement("input", { value: form.banner_path, onChange: (event) => setForm((current) => ({ ...current, banner_path: event.target.value })), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Featured artwork"), /* @__PURE__ */ React.createElement(NovaSelect, { value: String(form.featured_artwork_id || ""), onChange: (val) => setForm((current) => ({ ...current, featured_artwork_id: val })), placeholder: "Use latest published artwork", options: featuredArtworkOptions.map((item) => ({ value: String(item.id), label: item.title })) })), selectedFeaturedArtwork ? /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 rounded-[20px] border border-white/10 bg-white/[0.04] p-3" }, selectedFeaturedArtwork.thumb ? /* @__PURE__ */ React.createElement("img", { src: selectedFeaturedArtwork.thumb, alt: selectedFeaturedArtwork.title, className: "h-16 w-16 rounded-2xl object-cover" }) : null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "font-semibold text-white" }, selectedFeaturedArtwork.title), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, selectedFeaturedArtwork.author || "Group member"))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "When this is empty, the public overview falls back to the latest published works automatically.")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Visibility"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.visibility, onChange: (val) => setForm((current) => ({ ...current, visibility: val })), options: props.visibilityOptions || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-200" }, /* @__PURE__ */ React.createElement("span", null, "Membership policy"), /* @__PURE__ */ React.createElement(NovaSelect, { value: form.membership_policy, onChange: (val) => setForm((current) => ({ ...current, membership_policy: val })), options: props.membershipPolicyOptions || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm text-slate-200" }, "Links"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: addLink, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white" }, "Add link")), form.links_json.map((item, index2) => /* @__PURE__ */ React.createElement("div", { key: `link-${index2}`, className: "grid gap-3 md:grid-cols-[0.8fr_1.2fr_auto]" }, /* @__PURE__ */ React.createElement("input", { value: item.label, onChange: (event) => updateLink(index2, "label", event.target.value), placeholder: "Label", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: item.url, onChange: (event) => updateLink(index2, "url", event.target.value), placeholder: "https://", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeLink(index2), className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Remove")))), /* @__PURE__ */ React.createElement("div", { className: "flex justify-between gap-3" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: archiveGroup, className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Archive group"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: submit, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100" }, "Save settings"))))); } -const __vite_glob_0_123 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_128 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupSettings }, Symbol.toStringTag, { value: "Module" })); @@ -96969,7 +101003,7 @@ function GroupCard({ group }) { return /* @__PURE__ */ React.createElement("article", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_50px_rgba(3,7,18,0.22)]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-14 w-14 items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-slate-900/70 text-slate-300" }, group.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: group.avatar_url, alt: group.name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-people-group" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("h2", { className: "truncate text-lg font-semibold text-white" }, group.name), group.viewer?.role ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-100" }, group.viewer.role) : null, Number(group.pending_invites_count || 0) > 0 ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100" }, Number(group.pending_invites_count), " pending invite", Number(group.pending_invites_count) === 1 ? "" : "s") : null), group.headline ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-300" }, group.headline) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, Number(group.counts?.artworks || 0).toLocaleString(), " artworks"), /* @__PURE__ */ React.createElement("span", null, Number(group.counts?.collections || 0).toLocaleString(), " collections"), /* @__PURE__ */ React.createElement("span", null, Number(group.counts?.followers || 0).toLocaleString(), " followers")))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.studio, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, "Open Studio"), /* @__PURE__ */ React.createElement("a", { href: group.urls?.studio_invitations, className: "rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.06]" }, "Invitations"), /* @__PURE__ */ React.createElement("a", { href: group.urls?.public, className: "rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.06]" }, "Public page"))); } function StudioGroupsIndex() { - const { props } = X(); + const { props } = X$1(); const groups = Array.isArray(props.groups) ? props.groups : []; const pendingInvites = Array.isArray(props.pendingInvites) ? props.pendingInvites : []; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement( @@ -96989,7 +101023,7 @@ function StudioGroupsIndex() { } ), /* @__PURE__ */ React.createElement("div", { className: "mb-6 flex items-center justify-between gap-3 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/80" }, "Collective publishing"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Launch and manage shared identities")), /* @__PURE__ */ React.createElement(xe, { href: props.endpoints?.create, className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, "Create group")), pendingInvites.length > 0 ? /* @__PURE__ */ React.createElement("section", { className: "mb-6 rounded-[28px] border border-amber-300/20 bg-amber-400/10 p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-amber-50" }, "Pending invites"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, pendingInvites.map((invite) => /* @__PURE__ */ React.createElement("article", { key: invite.id, className: "rounded-2xl border border-white/10 bg-black/20 p-4 text-white" }, /* @__PURE__ */ React.createElement("h3", { className: "text-base font-semibold" }, invite.group?.name), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-amber-50/80" }, "Role: ", invite.role), invite.invited_by ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-amber-50/70" }, "Invited by ", invite.invited_by.name || invite.invited_by.username) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(invite.accept_url), className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-2 text-sm font-semibold text-emerald-100" }, "Accept"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => At.post(invite.decline_url), className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-white" }, "Decline")))))) : null, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 xl:grid-cols-2" }, groups.length > 0 ? groups.map((group) => /* @__PURE__ */ React.createElement(GroupCard, { key: group.slug, group })) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/10 px-6 py-16 text-center text-slate-400" }, "No groups yet. Create one to start publishing collaboratively."))); } -const __vite_glob_0_124 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_129 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGroupsIndex }, Symbol.toStringTag, { value: "Module" })); @@ -97018,7 +101052,7 @@ function TrendBars({ title, subtitle, points, colorClass }) { }))); } function StudioGrowth() { - const { props } = X(); + const { props } = X$1(); const { summary, moduleFocus, checkpoints, opportunities, milestones, momentum, topContent, rangeDays } = props; const updateRange = (days) => { trackStudioEvent("studio_filter_used", { @@ -97090,7 +101124,7 @@ function StudioGrowth() { /* @__PURE__ */ React.createElement("div", { className: "mt-3 grid grid-cols-3 gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Views"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm font-semibold text-white" }, Number(item.metrics?.views || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Reactions"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm font-semibold text-white" }, Number(item.metrics?.appreciation || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, "Comments"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm font-semibold text-white" }, Number(item.metrics?.comments || 0).toLocaleString()))) )))))); } -const __vite_glob_0_125 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_130 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioGrowth }, Symbol.toStringTag, { value: "Module" })); @@ -97121,7 +101155,7 @@ const priorityClasses = { low: "border-white/10 bg-white/[0.03] text-slate-300" }; function StudioInbox() { - const { props } = X(); + const { props } = X$1(); const inbox = props.inbox || {}; const filters = inbox.filters || {}; const items = inbox.items || []; @@ -97150,7 +101184,7 @@ function StudioInbox() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description, actions: /* @__PURE__ */ React.createElement("button", { type: "button", onClick: markAllRead, disabled: marking, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-slate-100 disabled:opacity-50" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-check-double" }), marking ? "Updating..." : "Mark all read") }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Unread"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.unread_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "High priority"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.high_priority_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Comments"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.comment_count || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Followers"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.follower_count || 0).toLocaleString()))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[320px_minmax(0,1fr)]" }, /* @__PURE__ */ React.createElement("aside", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Filters"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilters({ q: event.target.value }), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white", placeholder: "Actor, title, or module" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.type || "all", onChange: (val) => updateFilters({ type: val }), options: inbox.type_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Module"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.module || "all", onChange: (val) => updateFilters({ module: val }), options: inbox.module_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Read state"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.read_state || "all", onChange: (val) => updateFilters({ read_state: val }), options: inbox.read_state_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Priority"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.priority || "all", onChange: (val) => updateFilters({ priority: val }), options: inbox.priority_options || [], searchable: false })))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Attention now"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (inbox.panels?.attention_now || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.url, className: "block rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, item.module_label)))))), /* @__PURE__ */ React.createElement("section", { className: "space-y-4" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: `rounded-[28px] border p-5 ${item.is_new ? "border-sky-300/20 bg-sky-300/10" : "border-white/10 bg-white/[0.03]"}` }, /* @__PURE__ */ React.createElement("div", { className: "flex gap-4" }, item.actor?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: item.actor.avatar_url, alt: item.actor.name || "Actor", className: "h-12 w-12 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-black/20 text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-bell" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, item.module_label), /* @__PURE__ */ React.createElement("span", { className: `inline-flex items-center rounded-full border px-2 py-1 ${priorityClasses[item.priority] || priorityClasses.low}` }, item.priority), item.is_new && /* @__PURE__ */ React.createElement("span", { className: "rounded-full bg-sky-300/20 px-2 py-1 text-sky-100" }, "Unread")), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-lg font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-slate-400" }, item.body), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap items-center gap-3 text-sm text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, formatDate$2(item.created_at)), item.actor?.name && /* @__PURE__ */ React.createElement("span", null, item.actor.name), /* @__PURE__ */ React.createElement("a", { href: item.url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-slate-200" }, "Open")))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/15 px-6 py-16 text-center text-slate-400" }, "No inbox items match this filter."), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) <= 1, onClick: () => updateFilters({ page: Math.max(1, (meta.current_page || 1) - 1) }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Previous"), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, "Page ", meta.current_page || 1, " of ", meta.last_page || 1), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) >= (meta.last_page || 1), onClick: () => updateFilters({ page: (meta.current_page || 1) + 1 }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Next")))))); } -const __vite_glob_0_126 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_131 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioInbox }, Symbol.toStringTag, { value: "Module" })); @@ -97252,13 +101286,13 @@ function NewsTagInputDialog({ open, preview, onClose, onConfirm }) { const backdropRef = reactExports.useRef(null); reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [onClose, open]); if (!open || !preview) return null; return reactDomExports.createPortal( @@ -97410,7 +101444,7 @@ function NewsTagInput({ options, selectedIds, newTagNames, onSelectedIdsChange, setQuery(""); setIsOpen(false); }, [combinedNames, pastePreview, syncNames]); - const handleKeyDown = reactExports.useCallback((event) => { + const handleKeyDown2 = reactExports.useCallback((event) => { if (event.key === "Escape") { setIsOpen(false); return; @@ -97471,7 +101505,7 @@ function NewsTagInput({ options, selectedIds, newTagNames, onSelectedIdsChange, setIsOpen(true); }, onFocus: () => setIsOpen(true), - onKeyDown: handleKeyDown, + onKeyDown: handleKeyDown2, onPaste: handlePaste, placeholder: "Search existing tags or type a new one", className: "w-full rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-sm text-white placeholder:text-white/45 focus:border-sky-400 focus:outline-none", @@ -97885,13 +101919,13 @@ Source article: }; reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [onClose, open]); if (!open) return null; return reactDomExports.createPortal( @@ -97977,7 +102011,7 @@ Source article: ); } function StudioNewsEditor() { - const { props } = X(); + const { props } = X$1(); const { toasts, push: pushToast, dismiss: dismissToast } = useToast(); const article = props.article || {}; const initialFormData = reactExports.useMemo(() => buildInitialFormData(article, props.defaultAuthor, props.typeOptions, props.oldInput || {}), [article, props.defaultAuthor, props.oldInput, props.typeOptions]); @@ -97994,7 +102028,7 @@ function StudioNewsEditor() { const [jsonImportError, setJsonImportError] = reactExports.useState(""); const lastSyncedArticleKeyRef = reactExports.useRef(articleSyncKey); const slugTouchedRef = reactExports.useRef(Boolean(String(article.slug || "").trim())); - const form = G(initialFormData); + const form = G$1(initialFormData); const normalizedInitialPayload = reactExports.useMemo(() => JSON.stringify(buildSubmitPayload(initialFormData)), [initialFormData]); const normalizedCurrentPayload = reactExports.useMemo(() => JSON.stringify(buildSubmitPayload(form.data)), [form.data]); const hasUnsavedChanges = normalizedCurrentPayload !== normalizedInitialPayload; @@ -98371,7 +102405,7 @@ function StudioNewsEditor() { } )); } -const __vite_glob_0_127 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_132 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioNewsEditor }, Symbol.toStringTag, { value: "Module" })); @@ -98400,7 +102434,7 @@ function statusTone$1(status2) { } } function StudioNewsIndex() { - const { props } = X(); + const { props } = X$1(); const items = Array.isArray(props.listing?.items) ? props.listing.items : []; const filters = props.listing?.filters || {}; const meta = props.listing?.meta || {}; @@ -98467,7 +102501,7 @@ function StudioNewsIndex() { } )), /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400 lg:text-right" }, Number(meta.total || 0).toLocaleString(), " articles"))), /* @__PURE__ */ React.createElement("section", { className: "mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: "overflow-hidden rounded-[24px] border border-white/10 bg-black/20 shadow-[0_18px_40px_rgba(2,6,23,0.18)]" }, /* @__PURE__ */ React.createElement("div", { className: "aspect-[16/9] bg-slate-950/60" }, item.cover_url ? /* @__PURE__ */ React.createElement("img", { src: item.cover_url, alt: item.title, className: "h-full w-full object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-full items-center justify-center text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-newspaper text-3xl" }))), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-white/70" }, item.type_label), /* @__PURE__ */ React.createElement("span", { className: `rounded-full border px-2.5 py-1 ${statusTone$1(item.editorial_status)}` }, item.editorial_status.replaceAll("_", " ")), item.is_pinned ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-300/20 bg-amber-400/10 px-2.5 py-1 text-amber-100" }, "Pinned") : null, item.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-400/10 px-2.5 py-1 text-emerald-100" }, "Featured") : null), /* @__PURE__ */ React.createElement("h3", { className: "mt-3 text-xl font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-3 text-sm text-slate-400" }, item.category_name ? /* @__PURE__ */ React.createElement("span", null, item.category_name) : null, /* @__PURE__ */ React.createElement("span", null, item.author_name), /* @__PURE__ */ React.createElement("span", null, formatDate$1(item.published_at))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: item.edit_url, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100" }, "Edit"), /* @__PURE__ */ React.createElement("a", { href: item.editorial_status === "published" ? item.public_url : item.preview_url, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white" }, item.editorial_status === "published" ? "View" : "Preview"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => deleteItem(item), className: "rounded-full border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100" }, "Trash"))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No News articles match the current filters."))); } -const __vite_glob_0_128 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_133 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioNewsIndex }, Symbol.toStringTag, { value: "Module" })); @@ -98475,11 +102509,11 @@ function replacePattern(pattern, token, value) { return String(pattern || "").replace(token, String(value)); } function StudioNewsTaxonomies() { - const { props } = X(); + const { props } = X$1(); const [categories, setCategories] = reactExports.useState(Array.isArray(props.categories) ? props.categories : []); const [tags, setTags] = reactExports.useState(Array.isArray(props.tags) ? props.tags : []); - const categoryForm = G({ name: "", slug: "", description: "", position: 0, is_active: true }); - const tagForm = G({ name: "", slug: "" }); + const categoryForm = G$1({ name: "", slug: "", description: "", position: 0, is_active: true }); + const tagForm = G$1({ name: "", slug: "" }); const updateCategory = (index2, field, value) => { setCategories((current) => current.map((item, itemIndex) => itemIndex === index2 ? { ...item, [field]: value } : item)); }; @@ -98500,7 +102534,7 @@ function StudioNewsTaxonomies() { tagForm.post(props.storeTagUrl); }, className: "mt-5 grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] md:items-center" }, /* @__PURE__ */ React.createElement("input", { value: tagForm.data.name, onChange: (event) => tagForm.setData("name", event.target.value), placeholder: "Tag name", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: tagForm.data.slug, onChange: (event) => tagForm.setData("slug", event.target.value), placeholder: "optional slug", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100" }, "Create tag")), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-3" }, tags.map((tag, index2) => /* @__PURE__ */ React.createElement("div", { key: tag.id, className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto_auto] md:items-center" }, /* @__PURE__ */ React.createElement("input", { value: tag.name, onChange: (event) => updateTag(index2, "name", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("input", { value: tag.slug, onChange: (event) => updateTag(index2, "slug", event.target.value), className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.14em] text-slate-500" }, Number(tag.published_count || 0).toLocaleString(), " published"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => saveTag(tag), className: "rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white" }, "Save")))))))); } -const __vite_glob_0_129 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_134 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioNewsTaxonomies }, Symbol.toStringTag, { value: "Module" })); @@ -98564,7 +102598,7 @@ async function requestJson$2(url, method, body2) { return payload; } function StudioPreferences() { - const { props } = X(); + const { props } = X$1(); const preferences = props.preferences || {}; const [form, setForm] = reactExports.useState({ default_content_view: preferences.default_content_view || "grid", @@ -98650,7 +102684,7 @@ function StudioPreferences() { return /* @__PURE__ */ React.createElement("div", { key: widgetKey, className: "flex flex-col gap-3 rounded-[22px] border border-white/10 bg-black/20 p-4 md:flex-row md:items-center md:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, option.label), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.16em] text-slate-500" }, "Position ", index2 + 1)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => toggleWidget(widgetKey), className: `rounded-full border px-3 py-1.5 text-xs ${enabled ? "border-sky-300/25 bg-sky-300/10 text-sky-100" : "border-white/10 text-slate-300"}` }, enabled ? "Visible" : "Hidden"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => moveWidget(widgetKey, "up"), className: "rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-300" }, "Up"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => moveWidget(widgetKey, "down"), className: "rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-300" }, "Down"))); })))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Related surfaces"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (props.links || []).map((link2) => /* @__PURE__ */ React.createElement("a", { key: link2.url, href: link2.url, className: "block rounded-[22px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: link2.icon }), /* @__PURE__ */ React.createElement("span", { className: "text-base font-semibold text-white" }, link2.label)))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Preference notes"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3 text-sm text-slate-400" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, "Landing page and widget order are stored in the shared Studio preference record, so new Creator Studio surfaces can plug into the same contract without another migration."), /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, "Analytics range and card density stay here so Analytics, Growth, and the main dashboard can stay visually consistent.")))))); } -const __vite_glob_0_130 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_135 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioPreferences }, Symbol.toStringTag, { value: "Module" })); @@ -98698,7 +102732,7 @@ function socialPlatformLabel(value) { return value.split(/[_-]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" "); } function StudioProfile() { - const { props } = X(); + const { props } = X$1(); const profile = props.profile || {}; const endpoints = props.endpoints || {}; const featuredContent = props.featuredContent || {}; @@ -98830,7 +102864,7 @@ function StudioProfile() { /* @__PURE__ */ React.createElement("div", { className: "p-6 pt-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-end gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "relative" }, profile.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: profile.avatar_url, alt: profile.username, className: "h-24 w-24 rounded-[28px] border border-white/10 object-cover shadow-lg" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-24 w-24 items-center justify-center rounded-[28px] border border-white/10 bg-black/30 text-slate-400 shadow-lg" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user text-2xl" })), /* @__PURE__ */ React.createElement("input", { ref: avatarInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleAvatarSelected, className: "hidden" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => avatarInputRef.current?.click(), disabled: uploadingAvatar, className: "absolute -bottom-2 -right-2 inline-flex h-10 w-10 items-center justify-center rounded-full border border-sky-300/25 bg-sky-300/15 text-sky-100 disabled:opacity-50" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${uploadingAvatar ? "fa-spinner fa-spin" : "fa-camera"}` }))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-3xl font-semibold text-white" }, profile.name), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-300" }, "@", profile.username), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap gap-4 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", null, Number(profile.followers || 0).toLocaleString(), " followers"), profile.location && /* @__PURE__ */ React.createElement("span", null, profile.location)))), profile.cover_url && /* @__PURE__ */ React.createElement("div", { className: "w-full max-w-sm rounded-[24px] border border-white/10 bg-black/30 p-4" }, /* @__PURE__ */ React.createElement("label", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, "Banner position"), /* @__PURE__ */ React.createElement("input", { type: "range", min: "0", max: "100", value: coverPosition, onChange: (event) => setCoverPosition(Number(event.target.value)), className: "mt-3 w-full" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveCoverPosition, disabled: savingCoverPosition, className: "mt-3 inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-white disabled:opacity-50" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrows-up-down" }), savingCoverPosition ? "Saving..." : "Save banner position")))) )), /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Public profile details"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Update the creator information that supports your public presence across Nova.")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: saveProfile, disabled: savingProfile, className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 disabled:opacity-50" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-floppy-disk" }), savingProfile ? "Saving..." : "Save profile")), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 md:grid-cols-2" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 md:col-span-2" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Display name"), /* @__PURE__ */ React.createElement("input", { value: form.display_name, onChange: (event) => setForm((current) => ({ ...current, display_name: event.target.value })), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none" })), /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 md:col-span-2" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Tagline"), /* @__PURE__ */ React.createElement("input", { value: form.tagline, onChange: (event) => setForm((current) => ({ ...current, tagline: event.target.value })), placeholder: "One-line creator summary", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500" })), /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 md:col-span-2" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Bio"), /* @__PURE__ */ React.createElement("textarea", { value: form.bio, onChange: (event) => setForm((current) => ({ ...current, bio: event.target.value })), rows: 5, placeholder: "Tell visitors what you create and what makes your work distinct.", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500" })), /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 md:col-span-2" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Website"), /* @__PURE__ */ React.createElement("input", { value: form.website, onChange: (event) => setForm((current) => ({ ...current, website: event.target.value })), placeholder: "https://example.com", className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500" }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-base font-semibold text-white" }, "Social links"), /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, "Add the channels that matter for your creator identity.")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: addSocialLink, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-white" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-plus" }), "Add link")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, form.social_links.map((link2, index2) => /* @__PURE__ */ React.createElement("div", { key: `${index2}-${link2.platform}`, className: "grid gap-3 rounded-[24px] border border-white/10 bg-black/20 p-4 md:grid-cols-[180px_minmax(0,1fr)_auto]" }, /* @__PURE__ */ React.createElement("input", { value: link2.platform, onChange: (event) => updateSocialLink(index2, "platform", event.target.value), placeholder: "instagram", className: "rounded-2xl border border-white/10 bg-black/30 px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500" }), /* @__PURE__ */ React.createElement("input", { value: link2.url, onChange: (event) => updateSocialLink(index2, "url", event.target.value), placeholder: "https://...", className: "rounded-2xl border border-white/10 bg-black/30 px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => removeSocialLink(index2), className: "inline-flex items-center justify-center rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-trash" }))))))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Publishing footprint"), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4" }, (props.moduleSummaries || []).map((item) => /* @__PURE__ */ React.createElement("div", { key: item.key, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-200" }, /* @__PURE__ */ React.createElement("i", { className: item.icon }), /* @__PURE__ */ React.createElement("span", null, item.label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(item.count || 0).toLocaleString()), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, Number(item.published_count || 0).toLocaleString(), " published, ", Number(item.draft_count || 0).toLocaleString(), " drafts"))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Featured identity"), /* @__PURE__ */ React.createElement("a", { href: "/studio/featured", className: "text-sm font-medium text-sky-100" }, "Manage featured")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, featuredModules.length > 0 ? featuredModules.map((module) => /* @__PURE__ */ React.createElement("span", { key: module, className: "inline-flex items-center rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-sky-100" }, socialPlatformLabel(module))) : /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No featured modules selected yet.")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, Object.entries(featuredContent).map(([module, item]) => item ? /* @__PURE__ */ React.createElement("a", { key: module, href: item.view_url || item.preview_url || "/studio/featured", className: "flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 p-3" }, item.image_url ? /* @__PURE__ */ React.createElement("img", { src: item.image_url, alt: item.title, className: "h-14 w-14 rounded-2xl object-cover" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-14 w-14 items-center justify-center rounded-2xl bg-white/5 text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: item.module_icon || "fa-solid fa-star" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, socialPlatformLabel(module)), /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, item.title))) : null))))))); } -const __vite_glob_0_131 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_136 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioProfile }, Symbol.toStringTag, { value: "Module" })); @@ -98852,7 +102886,7 @@ async function requestJson(url, method = "POST") { return payload; } function StudioScheduled() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const filters = listing.filters || {}; const summary = listing.summary || {}; @@ -98907,12 +102941,12 @@ function StudioScheduled() { }, [items, summary.next_publish_at]); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "grid gap-4 xl:grid-cols-[minmax(0,1fr)_340px]" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Scheduled total"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-3xl font-semibold text-white" }, Number(summary.total || 0).toLocaleString())), /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-black/20 p-4 md:col-span-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Next publish slot"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xl font-semibold text-white" }, formatReleaseCountdown(summary.next_publish_at, nowMs)), summary.next_publish_at && /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, formatScheduledDate(summary.next_publish_at)))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4" }, (summary.by_module || []).map((entry) => /* @__PURE__ */ React.createElement("div", { key: entry.key, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: entry.icon }), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-medium text-white" }, entry.label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-2xl font-semibold text-white" }, Number(entry.count || 0).toLocaleString()))))), /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Agenda"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, agenda.length > 0 ? agenda.slice(0, 6).map((day) => /* @__PURE__ */ React.createElement("div", { key: day.date, className: "rounded-[22px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold text-white" }, day.label), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, day.count, " items")), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-400" }, day.items.slice(0, 2).map((item) => item.title).join(" • ")))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[22px] border border-dashed border-white/15 px-4 py-8 text-sm text-slate-400" }, "No scheduled items yet.")))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_35%),linear-gradient(135deg,_rgba(15,23,42,0.86),_rgba(2,6,23,0.96))] p-5 lg:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-5" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search scheduled work"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilters({ q: event.target.value }), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white", placeholder: "Title or module" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Module"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.module || "all", onChange: (val) => updateFilters({ module: val }), options: listing.module_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Date range"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.range || "upcoming", onChange: (val) => updateFilters({ range: val }), options: rangeOptions2, searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Start date"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: filters.start_date || "", onChange: (nextValue) => updateFilters({ range: "custom", start_date: nextValue }), mode: "date", placeholder: "Start date", clearable: true, className: "bg-black/20" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "End date"), /* @__PURE__ */ React.createElement(DateTimePicker, { value: filters.end_date || "", onChange: (nextValue) => updateFilters({ range: "custom", end_date: nextValue }), mode: "date", placeholder: "End date", clearable: true, className: "bg-black/20" })), /* @__PURE__ */ React.createElement("div", { className: "flex items-end" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => updateFilters({ q: "", module: "all", range: "upcoming", start_date: "", end_date: "" }), className: "w-full rounded-2xl border border-white/10 px-4 py-3 text-sm text-slate-200" }, "Reset")))), /* @__PURE__ */ React.createElement("section", { className: "space-y-4" }, items.length > 0 ? items.map((item) => /* @__PURE__ */ React.createElement("article", { key: item.id, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70" }, /* @__PURE__ */ React.createElement("span", null, item.module_label), /* @__PURE__ */ React.createElement("span", null, item.status)), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-xl font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-2 flex flex-wrap items-center gap-4 text-sm text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, formatReleaseCountdown(item.scheduled_at || item.published_at, nowMs)), /* @__PURE__ */ React.createElement("span", null, formatScheduledDate(item.scheduled_at || item.published_at)), item.visibility && /* @__PURE__ */ React.createElement("span", null, "Visibility: ", item.visibility), item.updated_at && /* @__PURE__ */ React.createElement("span", null, "Last edited ", formatScheduledDate(item.updated_at)), item.schedule_timezone && /* @__PURE__ */ React.createElement("span", null, item.schedule_timezone))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: item.edit_url || item.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-slate-200" }, "Edit"), /* @__PURE__ */ React.createElement("a", { href: item.edit_url || item.manage_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-slate-200" }, "Reschedule"), item.preview_url && /* @__PURE__ */ React.createElement("a", { href: item.preview_url, className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-slate-200" }, "Preview"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: busyId === `publish:${item.id}`, onClick: () => runAction(item, "publish"), className: "inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm text-sky-100 disabled:opacity-50" }, "Publish now"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: busyId === `unschedule:${item.id}`, onClick: () => runAction(item, "unschedule"), className: "inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-slate-200 disabled:opacity-50" }, "Unschedule"))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/15 px-6 py-16 text-center text-slate-400" }, "No scheduled content matches this view.")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between rounded-[24px] border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) <= 1, onClick: () => updateFilters({ page: Math.max(1, (meta.current_page || 1) - 1) }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Previous"), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, "Page ", meta.current_page || 1, " of ", meta.last_page || 1), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: (meta.current_page || 1) >= (meta.last_page || 1), onClick: () => updateFilters({ page: (meta.current_page || 1) + 1 }), className: "rounded-full border border-white/10 px-4 py-2 disabled:opacity-40" }, "Next")))); } -const __vite_glob_0_132 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_137 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioScheduled }, Symbol.toStringTag, { value: "Module" })); function StudioSearch() { - const { props } = X(); + const { props } = X$1(); const search2 = props.search || {}; const filters = search2.filters || {}; const sections = search2.sections || []; @@ -98925,20 +102959,20 @@ function StudioSearch() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_35%),linear-gradient(135deg,_rgba(15,23,42,0.86),_rgba(2,6,23,0.96))] p-5 lg:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-5" }, /* @__PURE__ */ React.createElement("label", { className: "space-y-2 text-sm text-slate-300 xl:col-span-3" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Search Studio"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilters({ q: event.target.value }), className: "w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white", placeholder: "Search content, comments, inbox, or assets" })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Surface"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.type || "all", onChange: (val) => updateFilters({ type: val }), options: search2.type_options || [], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "space-y-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "block text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, "Module"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.module || "all", onChange: (val) => updateFilters({ module: val }), options: search2.module_options || [], searchable: false })))), filters.q ? /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm text-slate-400" }, "Found ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, Number(search2.summary?.total || 0).toLocaleString()), " matches for ", /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, search2.summary?.query)), sections.length > 0 ? sections.map((section) => /* @__PURE__ */ React.createElement("section", { key: section.key, className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, section.label), /* @__PURE__ */ React.createElement("span", { className: "text-xs uppercase tracking-[0.18em] text-slate-500" }, section.count, " matches")), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3" }, section.items.map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.href, className: "block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded-2xl bg-white/[0.04] text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: item.icon })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-base font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs uppercase tracking-[0.18em] text-slate-500" }, item.subtitle), /* @__PURE__ */ React.createElement("p", { className: "mt-3 line-clamp-3 text-sm leading-6 text-slate-400" }, item.description)))))))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/15 px-6 py-16 text-center text-slate-400" }, "No results matched this search yet.")) : /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_320px]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Continue working"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3 md:grid-cols-2" }, (search2.empty_state?.continue_working || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.edit_url || item.manage_url, className: "block rounded-[24px] border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, item.module_label, " · ", item.workflow?.readiness?.label))))), /* @__PURE__ */ React.createElement("aside", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Stale drafts"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 space-y-3" }, (search2.empty_state?.stale_drafts || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.edit_url || item.manage_url, className: "block rounded-2xl border border-white/10 bg-black/20 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold text-white" }, item.title), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-slate-500" }, item.module_label))))), /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Quick create"), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-3" }, (props.quickCreate || []).map((item) => /* @__PURE__ */ React.createElement("a", { key: item.key, href: item.url, className: "inline-flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-100" }, /* @__PURE__ */ React.createElement("i", { className: item.icon }), /* @__PURE__ */ React.createElement("span", null, "New ", item.label))))))))); } -const __vite_glob_0_133 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_138 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioSearch }, Symbol.toStringTag, { value: "Module" })); function StudioSettings() { - const { props } = X(); + const { props } = X$1(); return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "System handoff"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-2xl text-sm leading-6 text-slate-400" }, "Studio now keeps creator workflow preferences in their own surface. This page stays focused on links out to adjacent dashboards and the control points that do not belong in the day-to-day workflow UI."), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-2" }, (props.links || []).map((link2) => /* @__PURE__ */ React.createElement("a", { key: link2.url, href: link2.url, className: "rounded-[22px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20 hover:bg-black/30" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-sky-100" }, /* @__PURE__ */ React.createElement("i", { className: link2.icon }), /* @__PURE__ */ React.createElement("span", { className: "text-base font-semibold text-white" }, link2.label)), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, "Open the linked dashboard or settings surface without losing the Studio navigation shell as the default control plane."))))), /* @__PURE__ */ React.createElement("section", { className: "space-y-6" }, (props.sections || []).map((section) => /* @__PURE__ */ React.createElement("div", { key: section.title, className: "rounded-[30px] border border-white/10 bg-white/[0.03] p-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold text-white" }, section.title), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6 text-slate-400" }, section.body), /* @__PURE__ */ React.createElement("a", { href: section.href, className: "mt-4 inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, section.cta, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }))))))); } -const __vite_glob_0_134 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_139 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioSettings }, Symbol.toStringTag, { value: "Module" })); function StudioStories() { - const { props } = X(); + const { props } = X$1(); const summary = props.summary || {}; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "mb-6 grid gap-4 md:grid-cols-4" }, [ ["Stories", summary.count, "fa-solid fa-feather-pointed"], @@ -98946,7 +102980,7 @@ function StudioStories() { ["Published", summary.published_count, "fa-solid fa-sparkles"] ].map(([label, value, icon]) => /* @__PURE__ */ React.createElement("div", { key: label, className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: icon }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, label)), /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-3xl font-semibold text-white" }, Number(value || 0).toLocaleString()))), /* @__PURE__ */ React.createElement("a", { href: props.dashboardUrl, className: "rounded-[24px] border border-sky-300/20 bg-sky-300/10 p-5 text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em]" }, "Story dashboard"), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-6" }, "Jump into the existing story workspace when you need the full editor and publishing controls."))), /* @__PURE__ */ React.createElement(StudioContentBrowser, { listing: props.listing, quickCreate: props.quickCreate, hideModuleFilter: true })); } -const __vite_glob_0_135 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_140 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioStories }, Symbol.toStringTag, { value: "Module" })); @@ -99033,7 +103067,7 @@ function SummaryCard({ label, value, hint }) { return /* @__PURE__ */ React.createElement("div", { className: "rounded-[24px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500" }, label), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-3xl font-semibold text-white" }, value), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, hint)); } function StudioUploadQueue() { - const { props } = X(); + const { props } = X$1(); const queueProp = props.queue || {}; const chunkSize = Math.max(MIN_CHUNK_SIZE_BYTES$2, Number(props.chunkSize || 0) || 5 * 1024 * 1024); const chunkRequestTimeoutMs = Math.max(15e3, Number(props.chunkRequestTimeoutMs || 0) || 45e3); @@ -99609,7 +103643,7 @@ function StudioUploadQueue() { ))))); }))))); } -const __vite_glob_0_136 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_141 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioUploadQueue }, Symbol.toStringTag, { value: "Module" })); @@ -100553,7 +104587,7 @@ function LinkedChallengeEntryVisibilityManager({ challenge, hiddenIds, onToggle, })), error ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-sm text-rose-300" }, error) : null); } function StudioWorldEditor() { - const { props } = X(); + const { props } = X$1(); const world = props.world || null; const filesBaseUrl = props.mediaSupport?.files_base_url || ""; const sectionOptions = props.sectionOptions || []; @@ -100572,7 +104606,7 @@ function StudioWorldEditor() { is_featured: Boolean(relation.is_featured), preview: relation.preview || null }))) : []; - const form = G({ + const form = G$1({ title: world?.title || "", slug: world?.slug || "", tagline: world?.tagline || "", @@ -101341,7 +105375,7 @@ function StudioWorldEditor() { )) : null )); } -const __vite_glob_0_137 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_142 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioWorldEditor }, Symbol.toStringTag, { value: "Module" })); @@ -101418,7 +105452,7 @@ function WorldAnalyticsPortfolioPanel({ analytics = null }) { }))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4" }, summaryCards2.map((card) => /* @__PURE__ */ React.createElement(WorldAnalyticsSummaryCard, { key: card.label, ...card }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 grid gap-4 xl:grid-cols-2" }, /* @__PURE__ */ React.createElement(LeaderboardColumn, { title: "Top by views", rows: leaderboards.views || [], metricKey: "views" }), /* @__PURE__ */ React.createElement(LeaderboardColumn, { title: "Top by unique visitors", rows: leaderboards.unique_visitors || [], metricKey: "unique_visitors" }), /* @__PURE__ */ React.createElement(LeaderboardColumn, { title: "Top by submissions", rows: leaderboards.submissions || [], metricKey: "submissions" }), /* @__PURE__ */ React.createElement(LeaderboardColumn, { title: "Best view-to-submit conversion", rows: leaderboards.conversion || [], metricKey: "conversion" }))); } function StudioWorldsIndex() { - const { props } = X(); + const { props } = X$1(); const listing = props.listing || {}; const items = Array.isArray(listing.items) ? listing.items : []; const filters = listing.filters || {}; @@ -101427,7 +105461,7 @@ function StudioWorldsIndex() { }; return /* @__PURE__ */ React.createElement(StudioLayout, { title: props.title, subtitle: props.description }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-6" }, /* @__PURE__ */ React.createElement(WorldAnalyticsPortfolioPanel, { analytics: props.analytics }), /* @__PURE__ */ React.createElement("section", { className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_12rem_12rem_auto] lg:items-end" }, /* @__PURE__ */ React.createElement("label", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Search"), /* @__PURE__ */ React.createElement("input", { value: filters.q || "", onChange: (event) => updateFilter("q", event.target.value), placeholder: "Search title, slug, or summary", className: "rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Status"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.status || "", onChange: (val) => updateFilter("status", val), options: [{ value: "", label: "All statuses" }, ...props.statusOptions || []], searchable: false })), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 text-sm text-slate-300" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500" }, "Type"), /* @__PURE__ */ React.createElement(NovaSelect, { value: filters.type || "", onChange: (val) => updateFilter("type", val), options: [{ value: "", label: "All types" }, ...props.typeOptions || []], searchable: false })), /* @__PURE__ */ React.createElement("a", { href: props.createUrl, className: "inline-flex items-center justify-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-plus" }), "New world"))), /* @__PURE__ */ React.createElement("section", { className: "grid gap-4 xl:grid-cols-2" }, items.length > 0 ? items.map((world) => /* @__PURE__ */ React.createElement("a", { key: world.id, href: world.edit_url, className: "rounded-[28px] border border-white/10 bg-white/[0.03] p-5 transition hover:-translate-y-1 hover:border-white/20 hover:bg-white/[0.05]" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500" }, /* @__PURE__ */ React.createElement(WorldStatusBadge, { badge: { label: world.status, tone: "slate" } }), /* @__PURE__ */ React.createElement(WorldStatusBadge, { badge: { label: world.type, tone: "slate" } }), (Array.isArray(world.status_badges) ? world.status_badges : []).map((badge) => /* @__PURE__ */ React.createElement(WorldStatusBadge, { key: `${world.id}-${badge.label}`, badge }))), /* @__PURE__ */ React.createElement("h2", { className: "mt-4 text-2xl font-semibold tracking-[-0.03em] text-white" }, world.title), /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-500" }, "/", world.slug), world.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-4 text-sm leading-6 text-slate-300" }, world.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-4 text-sm text-slate-400" }, world.timeframe_label ? /* @__PURE__ */ React.createElement("span", null, world.timeframe_label) : null, world.promotion_window_label ? /* @__PURE__ */ React.createElement("span", null, world.promotion_window_label) : null, /* @__PURE__ */ React.createElement("span", null, world.relation_count, " relations"), world.live_submission_count > 0 ? /* @__PURE__ */ React.createElement("span", null, world.live_submission_count, " live submissions") : null, world.theme_key ? /* @__PURE__ */ React.createElement("span", null, world.theme_key) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-3 text-sm font-semibold" }, /* @__PURE__ */ React.createElement("span", { className: "text-sky-100" }, "Edit"), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "Preview"), world.public_url ? /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "Public") : null))) : /* @__PURE__ */ React.createElement("div", { className: "rounded-[28px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400" }, "No worlds match this filter yet.")))); } -const __vite_glob_0_138 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_143 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: StudioWorldsIndex }, Symbol.toStringTag, { value: "Module" })); @@ -101638,13 +105672,13 @@ function PastedTagsDialog({ open, preview, maxTags, onClose, onConfirm }) { }, [open]); reactExports.useEffect(() => { if (!open) return void 0; - const handleKeyDown = (event) => { + const handleKeyDown2 = (event) => { if (event.key === "Escape") { onClose?.(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener("keydown", handleKeyDown2); + return () => window.removeEventListener("keydown", handleKeyDown2); }, [onClose, open]); if (!open || !preview) return null; const duplicateCount = preview.skippedDuplicates.length; @@ -105726,7 +109760,7 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, chunkRequestTimeout }; } function UploadPage({ draftId, filesCdnUrl, chunkSize, chunkRequestTimeoutMs }) { - const { props } = X(); + const { props } = X$1(); const pageTitle = "Upload Artwork — Creator Studio"; const pageDescription = "Submit a new artwork, complete the required metadata, and publish it from Skinbase Creator Studio."; const windowFlags = window?.SKINBASE_FLAGS || {}; @@ -106034,7 +110068,7 @@ function UploadPage({ draftId, filesCdnUrl, chunkSize, chunkRequestTimeoutMs }) "Reset" )))), /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/5 p-6" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold" }, "Pipeline status"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white/60" }, "Stage: ", /* @__PURE__ */ React.createElement("span", { className: "text-white" }, statusLabel2)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 h-2 w-full overflow-hidden rounded-full bg-white/10" }, /* @__PURE__ */ React.createElement("div", { className: "h-full bg-sky-400 transition-all", style: { width: `${state.progress}%` } })), state.failureReason && /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-sm text-red-200" }, "Failure: ", state.failureReason), state.previewUrl && state.phase === phases.success && /* @__PURE__ */ React.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-semibold text-white/80" }, "CDN preview"), /* @__PURE__ */ React.createElement("div", { className: "mt-2 overflow-hidden rounded-xl border border-white/10" }, /* @__PURE__ */ React.createElement("img", { src: state.previewUrl, alt: "CDN preview", className: "h-56 w-full object-cover" }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-xs text-white/60" }, "Session: ", state.sessionId ?? "—")), /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-white/10 bg-white/5 p-6" }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold" }, "Draft resume"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-white/60" }, "Use the draft link to resume an interrupted upload."), draftId ? /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-sm text-white/80" }, "Draft ID: ", draftId) : /* @__PURE__ */ React.createElement("div", { className: "mt-4 text-sm text-white/50" }, "No draft loaded.")))))); } -const __vite_glob_0_139 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_144 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: UploadPage }, Symbol.toStringTag, { value: "Module" })); @@ -106209,7 +110243,7 @@ function WorldsIndexSection({ title, description, items = [], emptyMessage = "", return /* @__PURE__ */ React.createElement("section", { className: "mt-10" }, /* @__PURE__ */ React.createElement("div", { className: "mb-5 flex items-end justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold tracking-[-0.03em] text-white" }, title), description ? /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, description) : null), /* @__PURE__ */ React.createElement("div", { className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500" }, items.length, " ", countLabel)), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 xl:grid-cols-3" }, items.map((item) => renderItem(item, { sourceSurface, sourceDetail })))); } function WorldIndex() { - const { props } = X(); + const { props } = X$1(); const hasSpotlight = Boolean(props.spotlightWorld); const featuredFallback = !hasSpotlight && Array.isArray(props.featuredWorlds) ? props.featuredWorlds : []; return /* @__PURE__ */ React.createElement("main", { className: "min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(249,115,22,0.12),_transparent_28%),radial-gradient(circle_at_top_right,_rgba(56,189,248,0.12),_transparent_32%),linear-gradient(180deg,_#020617_0%,_#02040a_100%)] px-4 py-10 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement(SeoHead, { title: props.seo?.title || "Worlds - Skinbase", description: props.seo?.description || props.description, image: props.seo?.image }), /* @__PURE__ */ React.createElement("div", { className: "mx-auto max-w-7xl" }, /* @__PURE__ */ React.createElement("section", { className: "rounded-[36px] border border-white/10 bg-white/[0.03] p-6 sm:p-8" }, /* @__PURE__ */ React.createElement("div", { className: "max-w-4xl" }, /* @__PURE__ */ React.createElement("p", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/70" }, "Skinbase Worlds"), /* @__PURE__ */ React.createElement("h1", { className: "mt-4 text-4xl font-semibold tracking-[-0.05em] text-white sm:text-5xl" }, "Curated spaces for seasonal culture, scene moments, and editorial campaigns."), /* @__PURE__ */ React.createElement("p", { className: "mt-5 max-w-3xl text-base leading-7 text-slate-300" }, "Worlds bundle together artworks, collections, creators, groups, cards, releases, events, challenges, and newsroom context into a single themed destination. They are not filters. They are editorial environments."))), hasSpotlight ? /* @__PURE__ */ React.createElement("section", { className: "mt-8" }, /* @__PURE__ */ React.createElement( @@ -106278,7 +110312,7 @@ function WorldIndex() { } ))); } -const __vite_glob_0_140 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_145 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: WorldIndex }, Symbol.toStringTag, { value: "Module" })); @@ -106479,7 +110513,7 @@ function RewardedContributors({ section, world }) { return /* @__PURE__ */ React.createElement("section", { className: "mt-10 rounded-[28px] border border-white/10 bg-white/[0.03] p-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-semibold tracking-[-0.03em] text-white" }, "Rewarded Contributors"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, "Creators who earned visible recognition in this edition. Live participation builds history here, while featured and editorial selections raise the level of recognition.")), summaryChips.length > 0 || rewardTypeChips.length > 0 || world?.cta_url ? /* @__PURE__ */ React.createElement("div", { className: "mb-5 rounded-[24px] border border-white/10 bg-black/20 p-4" }, summaryChips.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-2" }, summaryChips.map((item) => /* @__PURE__ */ React.createElement("span", { key: item, className: "rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-200" }, item))) : null, rewardTypeChips.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, rewardTypeChips.map((item) => /* @__PURE__ */ React.createElement("span", { key: item, className: "rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-sky-100" }, item))) : null, world?.cta_url ? /* @__PURE__ */ React.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React.createElement("a", { href: world.cta_url, "data-world-event": "world_cta_clicked", "data-world-section-key": "rewards", "data-world-cta-key": "rewards_join_world", className: "inline-flex items-center gap-2 rounded-full border border-white/12 bg-white/[0.06] px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-white transition hover:bg-white/[0.1]" }, world.cta_label || "Join this world", /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-arrow-right" }))) : null) : null, /* @__PURE__ */ React.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React.createElement("p", { className: "max-w-3xl text-sm leading-6 text-slate-400" }, "This edition’s rewards are edition-aware, so recognition here remains part of each creator’s recurring world history.")), /* @__PURE__ */ React.createElement("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3" }, items.map((item) => /* @__PURE__ */ React.createElement("a", { key: item.id, href: item.creator?.profile_url || item.world?.url || "#", "data-world-event": "world_entity_clicked", "data-world-section-key": "rewards", "data-world-entity-type": "creator", "data-world-entity-id": item.creator?.id || 0, "data-world-entity-title": item.creator?.name || item.creator?.username || "Creator", className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/15 hover:bg-white/[0.06]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, item.creator?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: item.creator.avatar_url, alt: item.creator.username || item.creator.name, className: "h-12 w-12 rounded-2xl object-cover ring-1 ring-white/10" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.04] text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, item.creator?.name || item.creator?.username || "Creator"), /* @__PURE__ */ React.createElement("div", { className: "truncate text-xs uppercase tracking-[0.16em] text-slate-500" }, item.badge_label))), item.artwork?.title ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-sm text-slate-300" }, item.artwork.title) : null)))); } function WorldShow() { - const { props } = X(); + const { props } = X$1(); const world = props.world; const recap = props.recap || null; const sections = Array.isArray(props.sections) ? props.sections : []; @@ -106572,7 +110606,7 @@ function WorldShow() { } ))); } -const __vite_glob_0_141 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const __vite_glob_0_146 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: WorldShow }, Symbol.toStringTag, { value: "Module" })); @@ -106952,9 +110986,9 @@ function requireReactDomServerLegacy_node_production() { null != formData && formData.forEach(validateAdditionalFormField); } return customFields; - } catch (x) { - if ("object" === typeof x && null !== x && "function" === typeof x.then) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) + throw x2; } } return null; @@ -108774,7 +112808,7 @@ function requireReactDomServerLegacy_node_production() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -108868,9 +112902,9 @@ function requireReactDomServerLegacy_node_production() { }; } var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log = Math.log, LN2 = Math.LN2; - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } function noop2() { } @@ -108922,8 +112956,8 @@ function requireReactDomServerLegacy_node_production() { suspendedThenable = null; return thenable; } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } var objectIs = "function" === typeof Object.is ? Object.is : is, currentlyRenderingComponent = null, currentlyRenderingTask = null, currentlyRenderingRequest = null, currentlyRenderingKeyPath = null, firstWorkInProgressHook = null, workInProgressHook = null, isReRender = false, didScheduleRenderPhaseUpdate = false, localIdCounter = 0, actionStateCounter = 0, actionStateMatchingIndex = -1, thenableIndexCounter = 0, thenableState = null, renderPhaseUpdates = null, numberOfReRenders = 0; function resolveCurrentlyRenderingComponent() { @@ -109184,10 +113218,10 @@ function requireReactDomServerLegacy_node_production() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -109213,8 +113247,8 @@ function requireReactDomServerLegacy_node_production() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -109297,7 +113331,7 @@ function requireReactDomServerLegacy_node_production() { lazyComponent = lazyComponent._init; try { type2 = lazyComponent(payload); - } catch (x) { + } catch (x2) { return describeBuiltInComponentFrame("Lazy"); } return describeComponentStackByType(type2); @@ -109535,8 +113569,8 @@ function requireReactDomServerLegacy_node_production() { info += describeComponentStackByType(node2.type), node2 = node2.parent; while (node2); var JSCompiler_inline_result = info; - } catch (x) { - JSCompiler_inline_result = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + JSCompiler_inline_result = "\nError generating stack: " + x2.message + "\n" + x2.stack; } Object.defineProperty(errorInfo, "componentStack", { value: JSCompiler_inline_result @@ -110193,14 +114227,14 @@ function requireReactDomServerLegacy_node_production() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(node2, 1), x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(node2, 1), x2; task.replay.pendingTasks--; props = getThrownInfo(task.componentStack); key = request; request = task.blockedBoundary; - type2 = x; + type2 = x2; props = logRecoverableError(key, type2, props); abortRemainingReplayNodes( key, @@ -110365,8 +114399,8 @@ function requireReactDomServerLegacy_node_production() { function renderChildrenArray(request, task, children, childIndex) { var prevKeyPath = task.keyPath; if (-1 !== childIndex && (task.keyPath = [task.keyPath, "Fragment", childIndex], null !== task.replay)) { - for (var replay = task.replay, replayNodes = replay.nodes, j = 0; j < replayNodes.length; j++) { - var node2 = replayNodes[j]; + for (var replay = task.replay, replayNodes = replay.nodes, j2 = 0; j2 < replayNodes.length; j2++) { + var node2 = replayNodes[j2]; if (node2[1] === childIndex) { childIndex = node2[2]; node2 = node2[3]; @@ -110378,12 +114412,12 @@ function requireReactDomServerLegacy_node_production() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw x2; task.replay.pendingTasks--; children = getThrownInfo(task.componentStack); - var boundary = task.blockedBoundary, error = x; + var boundary = task.blockedBoundary, error = x2; children = logRecoverableError(request, error, children); abortRemainingReplayNodes( request, @@ -110395,7 +114429,7 @@ function requireReactDomServerLegacy_node_production() { ); } task.replay = replay; - replayNodes.splice(j, 1); + replayNodes.splice(j2, 1); break; } } @@ -110404,15 +114438,15 @@ function requireReactDomServerLegacy_node_production() { } replay = task.treeContext; replayNodes = children.length; - if (null !== task.replay && (j = task.replay.slots, null !== j && "object" === typeof j)) { + if (null !== task.replay && (j2 = task.replay.slots, null !== j2 && "object" === typeof j2)) { for (childIndex = 0; childIndex < replayNodes; childIndex++) - node2 = children[childIndex], task.treeContext = pushTreeContext(replay, replayNodes, childIndex), boundary = j[childIndex], "number" === typeof boundary ? (resumeNode(request, task, boundary, node2, childIndex), delete j[childIndex]) : renderNode(request, task, node2, childIndex); + node2 = children[childIndex], task.treeContext = pushTreeContext(replay, replayNodes, childIndex), boundary = j2[childIndex], "number" === typeof boundary ? (resumeNode(request, task, boundary, node2, childIndex), delete j2[childIndex]) : renderNode(request, task, node2, childIndex); task.treeContext = replay; task.keyPath = prevKeyPath; return; } - for (j = 0; j < replayNodes; j++) - childIndex = children[j], task.treeContext = pushTreeContext(replay, replayNodes, j), renderNode(request, task, childIndex, j); + for (j2 = 0; j2 < replayNodes; j2++) + childIndex = children[j2], task.treeContext = pushTreeContext(replay, replayNodes, j2), renderNode(request, task, childIndex, j2); task.treeContext = replay; task.keyPath = prevKeyPath; } @@ -110839,17 +114873,17 @@ function requireReactDomServerLegacy_node_production() { ); } catch (thrownValue) { resetHooksState(); - var x = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; - if ("object" === typeof x && null !== x && "function" === typeof x.then) { + var x2 = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) { var ping = task.ping; - x.then(ping, ping); + x2.then(ping, ping); task.thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; } else { task.replay.pendingTasks--; task.abortSet.delete(task); var errorInfo = getThrownInfo(task.componentStack); request = void 0; - var request$jscomp$1 = request$jscomp$0, boundary = task.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x, replayNodes = task.replay.nodes, resumeSlots = task.replay.slots; + var request$jscomp$1 = request$jscomp$0, boundary = task.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x2, replayNodes = task.replay.nodes, resumeSlots = task.replay.slots; request = logRecoverableError( request$jscomp$1, error$jscomp$0, @@ -112004,9 +116038,9 @@ function requireReactDomServer_node_production() { null != formData && formData.forEach(validateAdditionalFormField); } return customFields; - } catch (x) { - if ("object" === typeof x && null !== x && "function" === typeof x.then) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) + throw x2; } } return null; @@ -113827,7 +117861,7 @@ function requireReactDomServer_node_production() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -113921,9 +117955,9 @@ function requireReactDomServer_node_production() { }; } var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log = Math.log, LN2 = Math.LN2; - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } function noop2() { } @@ -113975,8 +118009,8 @@ function requireReactDomServer_node_production() { suspendedThenable = null; return thenable; } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } var objectIs = "function" === typeof Object.is ? Object.is : is, currentlyRenderingComponent = null, currentlyRenderingTask = null, currentlyRenderingRequest = null, currentlyRenderingKeyPath = null, firstWorkInProgressHook = null, workInProgressHook = null, isReRender = false, didScheduleRenderPhaseUpdate = false, localIdCounter = 0, actionStateCounter = 0, actionStateMatchingIndex = -1, thenableIndexCounter = 0, thenableState = null, renderPhaseUpdates = null, numberOfReRenders = 0; function resolveCurrentlyRenderingComponent() { @@ -114248,10 +118282,10 @@ function requireReactDomServer_node_production() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -114277,8 +118311,8 @@ function requireReactDomServer_node_production() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -114361,7 +118395,7 @@ function requireReactDomServer_node_production() { lazyComponent = lazyComponent._init; try { type2 = lazyComponent(payload); - } catch (x) { + } catch (x2) { return describeBuiltInComponentFrame("Lazy"); } return describeComponentStackByType(type2); @@ -114722,8 +118756,8 @@ function requireReactDomServer_node_production() { info += describeComponentStackByType(node2.type), node2 = node2.parent; while (node2); var JSCompiler_inline_result = info; - } catch (x) { - JSCompiler_inline_result = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + JSCompiler_inline_result = "\nError generating stack: " + x2.message + "\n" + x2.stack; } Object.defineProperty(errorInfo, "componentStack", { value: JSCompiler_inline_result @@ -115365,14 +119399,14 @@ function requireReactDomServer_node_production() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(node2, 1), x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(node2, 1), x2; task.replay.pendingTasks--; props = getThrownInfo(task.componentStack); key = request; request = task.blockedBoundary; - type2 = x; + type2 = x2; props = logRecoverableError(key, type2, props); abortRemainingReplayNodes( key, @@ -115543,8 +119577,8 @@ function requireReactDomServer_node_production() { function renderChildrenArray(request, task, children, childIndex) { var prevKeyPath = task.keyPath; if (-1 !== childIndex && (task.keyPath = [task.keyPath, "Fragment", childIndex], null !== task.replay)) { - for (var replay = task.replay, replayNodes = replay.nodes, j = 0; j < replayNodes.length; j++) { - var node2 = replayNodes[j]; + for (var replay = task.replay, replayNodes = replay.nodes, j2 = 0; j2 < replayNodes.length; j2++) { + var node2 = replayNodes[j2]; if (node2[1] === childIndex) { childIndex = node2[2]; node2 = node2[3]; @@ -115556,12 +119590,12 @@ function requireReactDomServer_node_production() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw x2; task.replay.pendingTasks--; children = getThrownInfo(task.componentStack); - var boundary = task.blockedBoundary, error = x; + var boundary = task.blockedBoundary, error = x2; children = logRecoverableError(request, error, children); abortRemainingReplayNodes( request, @@ -115573,7 +119607,7 @@ function requireReactDomServer_node_production() { ); } task.replay = replay; - replayNodes.splice(j, 1); + replayNodes.splice(j2, 1); break; } } @@ -115582,15 +119616,15 @@ function requireReactDomServer_node_production() { } replay = task.treeContext; replayNodes = children.length; - if (null !== task.replay && (j = task.replay.slots, null !== j && "object" === typeof j)) { + if (null !== task.replay && (j2 = task.replay.slots, null !== j2 && "object" === typeof j2)) { for (childIndex = 0; childIndex < replayNodes; childIndex++) - node2 = children[childIndex], task.treeContext = pushTreeContext(replay, replayNodes, childIndex), boundary = j[childIndex], "number" === typeof boundary ? (resumeNode(request, task, boundary, node2, childIndex), delete j[childIndex]) : renderNode(request, task, node2, childIndex); + node2 = children[childIndex], task.treeContext = pushTreeContext(replay, replayNodes, childIndex), boundary = j2[childIndex], "number" === typeof boundary ? (resumeNode(request, task, boundary, node2, childIndex), delete j2[childIndex]) : renderNode(request, task, node2, childIndex); task.treeContext = replay; task.keyPath = prevKeyPath; return; } - for (j = 0; j < replayNodes; j++) - childIndex = children[j], task.treeContext = pushTreeContext(replay, replayNodes, j), renderNode(request, task, childIndex, j); + for (j2 = 0; j2 < replayNodes; j2++) + childIndex = children[j2], task.treeContext = pushTreeContext(replay, replayNodes, j2), renderNode(request, task, childIndex, j2); task.treeContext = replay; task.keyPath = prevKeyPath; } @@ -116025,17 +120059,17 @@ function requireReactDomServer_node_production() { ); } catch (thrownValue) { resetHooksState(); - var x = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; - if ("object" === typeof x && null !== x && "function" === typeof x.then) { + var x2 = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) { var ping = task.ping; - x.then(ping, ping); + x2.then(ping, ping); task.thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; } else { task.replay.pendingTasks--; task.abortSet.delete(task); var errorInfo = getThrownInfo(task.componentStack); request = void 0; - var request$jscomp$1 = request$jscomp$0, boundary = task.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x, replayNodes = task.replay.nodes, resumeSlots = task.replay.slots; + var request$jscomp$1 = request$jscomp$0, boundary = task.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x2, replayNodes = task.replay.nodes, resumeSlots = task.replay.slots; request = logRecoverableError( request$jscomp$1, error$jscomp$0, @@ -117234,7 +121268,7 @@ function requireReactDomServerLegacy_node_development() { type2 = type2._init; try { return describeElementType(type2(payload)); - } catch (x) { + } catch (x2) { } } return ""; @@ -117898,12 +121932,12 @@ function requireReactDomServerLegacy_node_development() { null != formData && formData.forEach(validateAdditionalFormField); } return customFields; - } catch (x) { - if ("object" === typeof x && null !== x && "function" === typeof x.then) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) + throw x2; console.error( "Failed to serialize an action for progressive enhancement:\n%s", - x + x2 ); } } @@ -119837,7 +123871,7 @@ function requireReactDomServerLegacy_node_development() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -119934,9 +123968,9 @@ function requireReactDomServerLegacy_node_development() { overflow: baseContext }; } - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } function noop2() { } @@ -119984,8 +124018,8 @@ function requireReactDomServerLegacy_node_development() { suspendedThenable = null; return thenable; } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } function resolveCurrentlyRenderingComponent() { if (null === currentlyRenderingComponent) @@ -120252,10 +124286,10 @@ function requireReactDomServerLegacy_node_development() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -120286,8 +124320,8 @@ function requireReactDomServerLegacy_node_development() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -120378,7 +124412,7 @@ function requireReactDomServerLegacy_node_development() { lazyComponent = lazyComponent._init; try { type2 = lazyComponent(payload); - } catch (x) { + } catch (x2) { return describeBuiltInComponentFrame("Lazy"); } return describeComponentStackByType(type2); @@ -120630,8 +124664,8 @@ function requireReactDomServerLegacy_node_development() { JSCompiler_inline_result.stack ) : JSCompiler_inline_result.stack)), (componentStack = componentStack.owner) && JSCompiler_temp_const && (info += "\n" + JSCompiler_temp_const); var JSCompiler_inline_result$jscomp$0 = info; - } catch (x) { - JSCompiler_inline_result$jscomp$0 = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + JSCompiler_inline_result$jscomp$0 = "\nError generating stack: " + x2.message + "\n" + x2.stack; } return JSCompiler_inline_result$jscomp$0; } @@ -120710,8 +124744,8 @@ function requireReactDomServerLegacy_node_development() { info += describeComponentStackByType(node2.type), node2 = node2.parent; while (node2); var stack = info; - } catch (x) { - stack = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + stack = "\nError generating stack: " + x2.message + "\n" + x2.stack; } Object.defineProperty(errorInfo, "componentStack", { value: stack @@ -121693,14 +125727,14 @@ function requireReactDomServerLegacy_node_development() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(i, 1), x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(i, 1), x2; task.replay.pendingTasks--; type2 = getThrownInfo(task.componentStack); props = request; request = task.blockedBoundary; - keyPath = x; + keyPath = x2; ref2 = name2; name2 = logRecoverableError(props, keyPath, type2, task.debugTask); abortRemainingReplayNodes( @@ -121975,8 +126009,8 @@ function requireReactDomServerLegacy_node_development() { var previousDebugTask = task.debugTask; pushServerComponentStack(task, task.node._debugInfo); if (-1 !== childIndex && (task.keyPath = [task.keyPath, "Fragment", childIndex], null !== task.replay)) { - for (var replay = task.replay, replayNodes = replay.nodes, j = 0; j < replayNodes.length; j++) { - var node2 = replayNodes[j]; + for (var replay = task.replay, replayNodes = replay.nodes, j2 = 0; j2 < replayNodes.length; j2++) { + var node2 = replayNodes[j2]; if (node2[1] === childIndex) { childIndex = node2[2]; node2 = node2[3]; @@ -121988,13 +126022,13 @@ function requireReactDomServerLegacy_node_development() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw x2; task.replay.pendingTasks--; var thrownInfo = getThrownInfo(task.componentStack); children = task.blockedBoundary; - var error = x, resumeSlots = node2; + var error = x2, resumeSlots = node2; node2 = logRecoverableError( request, error, @@ -122013,7 +126047,7 @@ function requireReactDomServerLegacy_node_development() { ); } task.replay = replay; - replayNodes.splice(j, 1); + replayNodes.splice(j2, 1); break; } } @@ -122024,21 +126058,21 @@ function requireReactDomServerLegacy_node_development() { } replay = task.treeContext; replayNodes = children.length; - if (null !== task.replay && (j = task.replay.slots, null !== j && "object" === typeof j)) { + if (null !== task.replay && (j2 = task.replay.slots, null !== j2 && "object" === typeof j2)) { for (childIndex = 0; childIndex < replayNodes; childIndex++) node2 = children[childIndex], task.treeContext = pushTreeContext( replay, replayNodes, childIndex - ), error = j[childIndex], "number" === typeof error ? (resumeNode(request, task, error, node2, childIndex), delete j[childIndex]) : renderNode(request, task, node2, childIndex); + ), error = j2[childIndex], "number" === typeof error ? (resumeNode(request, task, error, node2, childIndex), delete j2[childIndex]) : renderNode(request, task, node2, childIndex); task.treeContext = replay; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; task.debugTask = previousDebugTask; return; } - for (j = 0; j < replayNodes; j++) - childIndex = children[j], warnForMissingKey(request, task, childIndex), task.treeContext = pushTreeContext(replay, replayNodes, j), renderNode(request, task, childIndex, j); + for (j2 = 0; j2 < replayNodes; j2++) + childIndex = children[j2], warnForMissingKey(request, task, childIndex), task.treeContext = pushTreeContext(replay, replayNodes, j2), renderNode(request, task, childIndex, j2); task.treeContext = replay; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -122516,15 +126550,15 @@ function requireReactDomServerLegacy_node_development() { ); } catch (thrownValue) { resetHooksState(); - var x = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; - if ("object" === typeof x && null !== x && "function" === typeof x.then) { + var x2 = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) { var ping = request.ping; - x.then(ping, ping); + x2.then(ping, ping); request.thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; } else { request.replay.pendingTasks--; request.abortSet.delete(request); - var errorInfo = getThrownInfo(request.componentStack), errorDigest = void 0, request$jscomp$1 = request$jscomp$0, boundary = request.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x, errorInfo$jscomp$0 = errorInfo, replayNodes = request.replay.nodes, resumeSlots = request.replay.slots; + var errorInfo = getThrownInfo(request.componentStack), errorDigest = void 0, request$jscomp$1 = request$jscomp$0, boundary = request.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x2, errorInfo$jscomp$0 = errorInfo, replayNodes = request.replay.nodes, resumeSlots = request.replay.slots; errorDigest = logRecoverableError( request$jscomp$1, error$jscomp$0, @@ -124329,7 +128363,7 @@ function requireReactDomServer_node_development() { type2 = type2._init; try { return describeElementType(type2(payload)); - } catch (x) { + } catch (x2) { } } return ""; @@ -125161,12 +129195,12 @@ function requireReactDomServer_node_development() { null != formData && formData.forEach(validateAdditionalFormField); } return customFields; - } catch (x) { - if ("object" === typeof x && null !== x && "function" === typeof x.then) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) + throw x2; console.error( "Failed to serialize an action for progressive enhancement:\n%s", - x + x2 ); } } @@ -126977,7 +131011,7 @@ function requireReactDomServer_node_development() { type2 = type2._init; try { return getComponentNameFromType(type2(innerType)); - } catch (x) { + } catch (x2) { } } return null; @@ -127074,9 +131108,9 @@ function requireReactDomServer_node_development() { overflow: baseContext }; } - function clz32Fallback(x) { - x >>>= 0; - return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + function clz32Fallback(x2) { + x2 >>>= 0; + return 0 === x2 ? 32 : 31 - (log(x2) / LN2 | 0) | 0; } function noop2() { } @@ -127124,8 +131158,8 @@ function requireReactDomServer_node_development() { suspendedThenable = null; return thenable; } - function is(x, y) { - return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y; + function is(x2, y2) { + return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2; } function resolveCurrentlyRenderingComponent() { if (null === currentlyRenderingComponent) @@ -127399,10 +131433,10 @@ function requireReactDomServer_node_development() { if (void 0 === prefix) try { throw Error(); - } catch (x) { - var match = x.stack.trim().match(/\n( *(at )?)/); + } catch (x2) { + var match = x2.stack.trim().match(/\n( *(at )?)/); prefix = match && match[1] || ""; - suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + suffix = -1 < x2.stack.indexOf("\n at") ? " ()" : -1 < x2.stack.indexOf("@") ? "@unknown:0:0" : ""; } return "\n" + prefix + name2 + suffix; } @@ -127433,8 +131467,8 @@ function requireReactDomServer_node_development() { if ("object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(Fake, []); - } catch (x) { - var control = x; + } catch (x2) { + var control = x2; } Reflect.construct(fn, [], Fake); } else { @@ -127525,7 +131559,7 @@ function requireReactDomServer_node_development() { lazyComponent = lazyComponent._init; try { type2 = lazyComponent(payload); - } catch (x) { + } catch (x2) { return describeBuiltInComponentFrame("Lazy"); } return describeComponentStackByType(type2); @@ -127908,8 +131942,8 @@ function requireReactDomServer_node_development() { JSCompiler_inline_result.stack ) : JSCompiler_inline_result.stack)), (componentStack = componentStack.owner) && JSCompiler_temp_const && (info += "\n" + JSCompiler_temp_const); var JSCompiler_inline_result$jscomp$0 = info; - } catch (x) { - JSCompiler_inline_result$jscomp$0 = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + JSCompiler_inline_result$jscomp$0 = "\nError generating stack: " + x2.message + "\n" + x2.stack; } return JSCompiler_inline_result$jscomp$0; } @@ -127988,8 +132022,8 @@ function requireReactDomServer_node_development() { info += describeComponentStackByType(node2.type), node2 = node2.parent; while (node2); var stack = info; - } catch (x) { - stack = "\nError generating stack: " + x.message + "\n" + x.stack; + } catch (x2) { + stack = "\nError generating stack: " + x2.message + "\n" + x2.stack; } Object.defineProperty(errorInfo, "componentStack", { value: stack @@ -128957,14 +132991,14 @@ function requireReactDomServer_node_development() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(i, 1), x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw task.node === keyOrIndex ? task.replay = replay : childIndex.splice(i, 1), x2; task.replay.pendingTasks--; type2 = getThrownInfo(task.componentStack); props = request; request = task.blockedBoundary; - keyPath = x; + keyPath = x2; ref2 = name2; name2 = logRecoverableError(props, keyPath, type2, task.debugTask); abortRemainingReplayNodes( @@ -129245,8 +133279,8 @@ function requireReactDomServer_node_development() { var previousDebugTask = task.debugTask; pushServerComponentStack(task, task.node._debugInfo); if (-1 !== childIndex && (task.keyPath = [task.keyPath, "Fragment", childIndex], null !== task.replay)) { - for (var replay = task.replay, replayNodes = replay.nodes, j = 0; j < replayNodes.length; j++) { - var node2 = replayNodes[j]; + for (var replay = task.replay, replayNodes = replay.nodes, j2 = 0; j2 < replayNodes.length; j2++) { + var node2 = replayNodes[j2]; if (node2[1] === childIndex) { childIndex = node2[2]; node2 = node2[3]; @@ -129258,13 +133292,13 @@ function requireReactDomServer_node_development() { "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering." ); task.replay.pendingTasks--; - } catch (x) { - if ("object" === typeof x && null !== x && (x === SuspenseException || "function" === typeof x.then)) - throw x; + } catch (x2) { + if ("object" === typeof x2 && null !== x2 && (x2 === SuspenseException || "function" === typeof x2.then)) + throw x2; task.replay.pendingTasks--; var thrownInfo = getThrownInfo(task.componentStack); children = task.blockedBoundary; - var error = x, resumeSlots = node2; + var error = x2, resumeSlots = node2; node2 = logRecoverableError( request, error, @@ -129283,7 +133317,7 @@ function requireReactDomServer_node_development() { ); } task.replay = replay; - replayNodes.splice(j, 1); + replayNodes.splice(j2, 1); break; } } @@ -129294,21 +133328,21 @@ function requireReactDomServer_node_development() { } replay = task.treeContext; replayNodes = children.length; - if (null !== task.replay && (j = task.replay.slots, null !== j && "object" === typeof j)) { + if (null !== task.replay && (j2 = task.replay.slots, null !== j2 && "object" === typeof j2)) { for (childIndex = 0; childIndex < replayNodes; childIndex++) node2 = children[childIndex], task.treeContext = pushTreeContext( replay, replayNodes, childIndex - ), error = j[childIndex], "number" === typeof error ? (resumeNode(request, task, error, node2, childIndex), delete j[childIndex]) : renderNode(request, task, node2, childIndex); + ), error = j2[childIndex], "number" === typeof error ? (resumeNode(request, task, error, node2, childIndex), delete j2[childIndex]) : renderNode(request, task, node2, childIndex); task.treeContext = replay; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; task.debugTask = previousDebugTask; return; } - for (j = 0; j < replayNodes; j++) - childIndex = children[j], warnForMissingKey(request, task, childIndex), task.treeContext = pushTreeContext(replay, replayNodes, j), renderNode(request, task, childIndex, j); + for (j2 = 0; j2 < replayNodes; j2++) + childIndex = children[j2], warnForMissingKey(request, task, childIndex), task.treeContext = pushTreeContext(replay, replayNodes, j2), renderNode(request, task, childIndex, j2); task.treeContext = replay; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -129794,15 +133828,15 @@ function requireReactDomServer_node_development() { ); } catch (thrownValue) { resetHooksState(); - var x = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; - if ("object" === typeof x && null !== x && "function" === typeof x.then) { + var x2 = thrownValue === SuspenseException ? getSuspendedThenable() : thrownValue; + if ("object" === typeof x2 && null !== x2 && "function" === typeof x2.then) { var ping = request.ping; - x.then(ping, ping); + x2.then(ping, ping); request.thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; } else { request.replay.pendingTasks--; request.abortSet.delete(request); - var errorInfo = getThrownInfo(request.componentStack), errorDigest = void 0, request$jscomp$1 = request$jscomp$0, boundary = request.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x, errorInfo$jscomp$0 = errorInfo, replayNodes = request.replay.nodes, resumeSlots = request.replay.slots; + var errorInfo = getThrownInfo(request.componentStack), errorDigest = void 0, request$jscomp$1 = request$jscomp$0, boundary = request.blockedBoundary, error$jscomp$0 = 12 === request$jscomp$0.status ? request$jscomp$0.fatalError : x2, errorInfo$jscomp$0 = errorInfo, replayNodes = request.replay.nodes, resumeSlots = request.replay.slots; errorDigest = logRecoverableError( request$jscomp$1, error$jscomp$0, @@ -132143,17 +136177,17 @@ var hasRequiredServer_node; function requireServer_node() { if (hasRequiredServer_node) return server_node; hasRequiredServer_node = 1; - var l, s2; + var l3, s2; if (process.env.NODE_ENV === "production") { - l = requireReactDomServerLegacy_node_production(); + l3 = requireReactDomServerLegacy_node_production(); s2 = requireReactDomServer_node_production(); } else { - l = requireReactDomServerLegacy_node_development(); + l3 = requireReactDomServerLegacy_node_development(); s2 = requireReactDomServer_node_development(); } - server_node.version = l.version; - server_node.renderToString = l.renderToString; - server_node.renderToStaticMarkup = l.renderToStaticMarkup; + server_node.version = l3.version; + server_node.renderToString = l3.renderToString; + server_node.renderToStaticMarkup = l3.renderToStaticMarkup; server_node.renderToPipeableStream = s2.renderToPipeableStream; server_node.renderToReadableStream = s2.renderToReadableStream; server_node.resumeToPipeableStream = s2.resumeToPipeableStream; @@ -132164,151 +136198,156 @@ var server_nodeExports = requireServer_node(); const ReactDOMServer = /* @__PURE__ */ getDefaultExportFromCjs(server_nodeExports); const pages = /* @__PURE__ */ Object.assign({ "./Pages/Academy/ChallengeSubmit.jsx": __vite_glob_0_0, - "./Pages/Academy/Index.jsx": __vite_glob_0_1, - "./Pages/Academy/List.jsx": __vite_glob_0_2, - "./Pages/Academy/Pricing.jsx": __vite_glob_0_3, - "./Pages/Academy/Show.jsx": __vite_glob_0_4, - "./Pages/Admin/Academy/CrudForm.jsx": __vite_glob_0_5, - "./Pages/Admin/Academy/CrudIndex.jsx": __vite_glob_0_6, - "./Pages/Admin/Academy/Dashboard.jsx": __vite_glob_0_7, - "./Pages/Admin/Academy/LessonEditor.jsx": __vite_glob_0_8, - "./Pages/Admin/Academy/Submissions.jsx": __vite_glob_0_9, - "./Pages/Admin/AiBiography.jsx": __vite_glob_0_10, - "./Pages/Admin/Artworks.jsx": __vite_glob_0_11, - "./Pages/Admin/AuthAudit.jsx": __vite_glob_0_12, - "./Pages/Admin/DailyActivity.jsx": __vite_glob_0_13, - "./Pages/Admin/Dashboard.jsx": __vite_glob_0_14, - "./Pages/Admin/FeaturedArtworks.jsx": __vite_glob_0_15, - "./Pages/Admin/HomepageAnnouncements/Form.jsx": __vite_glob_0_16, - "./Pages/Admin/HomepageAnnouncements/Index.jsx": __vite_glob_0_17, - "./Pages/Admin/Settings.jsx": __vite_glob_0_18, - "./Pages/Admin/Stories.jsx": __vite_glob_0_19, - "./Pages/Admin/UploadQueue.jsx": __vite_glob_0_20, - "./Pages/Admin/UsernameQueue.jsx": __vite_glob_0_21, - "./Pages/Admin/Users/Index.jsx": __vite_glob_0_22, - "./Pages/Artwork/SimilarArtworksHeader.jsx": __vite_glob_0_23, - "./Pages/ArtworkPage.jsx": __vite_glob_0_24, - "./Pages/CategoriesPage.jsx": __vite_glob_0_25, - "./Pages/Collection/CollectionAnalytics.jsx": __vite_glob_0_26, - "./Pages/Collection/CollectionDashboard.jsx": __vite_glob_0_27, - "./Pages/Collection/CollectionFeaturedIndex.jsx": __vite_glob_0_28, - "./Pages/Collection/CollectionHistory.jsx": __vite_glob_0_29, - "./Pages/Collection/CollectionManage.jsx": __vite_glob_0_30, - "./Pages/Collection/CollectionSeriesShow.jsx": __vite_glob_0_31, - "./Pages/Collection/CollectionShow.jsx": __vite_glob_0_32, - "./Pages/Collection/CollectionStaffProgramming.jsx": __vite_glob_0_33, - "./Pages/Collection/CollectionStaffSurfaces.jsx": __vite_glob_0_34, - "./Pages/Collection/FeaturedArtworksAdmin.jsx": __vite_glob_0_35, - "./Pages/Collection/NovaCardsAdminIndex.jsx": __vite_glob_0_36, - "./Pages/Collection/NovaCardsAssetPackAdmin.jsx": __vite_glob_0_37, - "./Pages/Collection/NovaCardsChallengeAdmin.jsx": __vite_glob_0_38, - "./Pages/Collection/NovaCardsCollectionAdmin.jsx": __vite_glob_0_39, - "./Pages/Collection/NovaCardsTemplateAdmin.jsx": __vite_glob_0_40, - "./Pages/Collection/SavedCollections.jsx": __vite_glob_0_41, - "./Pages/Community/CommunityActivityPage.jsx": __vite_glob_0_42, - "./Pages/Community/LatestCommentsPage.jsx": __vite_glob_0_43, - "./Pages/Feed/FollowingFeed.jsx": __vite_glob_0_44, - "./Pages/Feed/HashtagFeed.jsx": __vite_glob_0_45, - "./Pages/Feed/SavedFeed.jsx": __vite_glob_0_46, - "./Pages/Feed/SearchFeed.jsx": __vite_glob_0_47, - "./Pages/Feed/TrendingFeed.jsx": __vite_glob_0_48, - "./Pages/Forum/ForumCategory.jsx": __vite_glob_0_49, - "./Pages/Forum/ForumEditPost.jsx": __vite_glob_0_50, - "./Pages/Forum/ForumIndex.jsx": __vite_glob_0_51, - "./Pages/Forum/ForumNewThread.jsx": __vite_glob_0_52, - "./Pages/Forum/ForumSection.jsx": __vite_glob_0_53, - "./Pages/Forum/ForumThread.jsx": __vite_glob_0_54, - "./Pages/Group/GroupChallengeShow.jsx": __vite_glob_0_55, - "./Pages/Group/GroupEventShow.jsx": __vite_glob_0_56, - "./Pages/Group/GroupFaqPage.jsx": __vite_glob_0_57, - "./Pages/Group/GroupHelpPage.jsx": __vite_glob_0_58, - "./Pages/Group/GroupIndex.jsx": __vite_glob_0_59, - "./Pages/Group/GroupPostShow.jsx": __vite_glob_0_60, - "./Pages/Group/GroupProjectShow.jsx": __vite_glob_0_61, - "./Pages/Group/GroupQuickstartPage.jsx": __vite_glob_0_62, - "./Pages/Group/GroupReleaseShow.jsx": __vite_glob_0_63, - "./Pages/Group/GroupShow.jsx": __vite_glob_0_64, - "./Pages/Help/AccountHelpPage.jsx": __vite_glob_0_65, - "./Pages/Help/AuthHelpPage.jsx": __vite_glob_0_66, - "./Pages/Help/CardsHelpPage.jsx": __vite_glob_0_67, - "./Pages/Help/HelpCenterPage.jsx": __vite_glob_0_68, - "./Pages/Help/ProfileHelpPage.jsx": __vite_glob_0_69, - "./Pages/Help/StudioHelpPage.jsx": __vite_glob_0_70, - "./Pages/Help/TroubleshootingHelpPage.jsx": __vite_glob_0_71, - "./Pages/Help/UploadHelpPage.jsx": __vite_glob_0_72, - "./Pages/Help/WorldsHelpPage.jsx": __vite_glob_0_73, - "./Pages/Leaderboard/LeaderboardPage.jsx": __vite_glob_0_74, - "./Pages/Messages/Index.jsx": __vite_glob_0_75, - "./Pages/Moderation/AiBiographyAdmin.jsx": __vite_glob_0_76, - "./Pages/Moderation/ArtworkMaturityQueue.jsx": __vite_glob_0_77, - "./Pages/News/NewsComments.jsx": __vite_glob_0_78, - "./Pages/Profile/ProfileGallery.jsx": __vite_glob_0_79, - "./Pages/Profile/ProfileShow.jsx": __vite_glob_0_80, - "./Pages/Settings/ProfileEdit.jsx": __vite_glob_0_81, - "./Pages/Studio/StudioActivity.jsx": __vite_glob_0_82, - "./Pages/Studio/StudioAnalytics.jsx": __vite_glob_0_83, - "./Pages/Studio/StudioArchived.jsx": __vite_glob_0_84, - "./Pages/Studio/StudioArtworkAnalytics.jsx": __vite_glob_0_85, - "./Pages/Studio/StudioArtworkEdit.jsx": __vite_glob_0_86, - "./Pages/Studio/StudioArtworks.jsx": __vite_glob_0_87, - "./Pages/Studio/StudioAssets.jsx": __vite_glob_0_88, - "./Pages/Studio/StudioCalendar.jsx": __vite_glob_0_89, - "./Pages/Studio/StudioCardAnalytics.jsx": __vite_glob_0_90, - "./Pages/Studio/StudioCardEditor.jsx": __vite_glob_0_91, - "./Pages/Studio/StudioCardsIndex.jsx": __vite_glob_0_92, - "./Pages/Studio/StudioChallenges.jsx": __vite_glob_0_93, - "./Pages/Studio/StudioCollections.jsx": __vite_glob_0_94, - "./Pages/Studio/StudioComments.jsx": __vite_glob_0_95, - "./Pages/Studio/StudioContentIndex.jsx": __vite_glob_0_96, - "./Pages/Studio/StudioDashboard.jsx": __vite_glob_0_97, - "./Pages/Studio/StudioDrafts.jsx": __vite_glob_0_98, - "./Pages/Studio/StudioFeatured.jsx": __vite_glob_0_99, - "./Pages/Studio/StudioFollowers.jsx": __vite_glob_0_100, - "./Pages/Studio/StudioGroupActivity.jsx": __vite_glob_0_101, - "./Pages/Studio/StudioGroupArtworks.jsx": __vite_glob_0_102, - "./Pages/Studio/StudioGroupAssets.jsx": __vite_glob_0_103, - "./Pages/Studio/StudioGroupChallengeEditor.jsx": __vite_glob_0_104, - "./Pages/Studio/StudioGroupChallenges.jsx": __vite_glob_0_105, - "./Pages/Studio/StudioGroupCollections.jsx": __vite_glob_0_106, - "./Pages/Studio/StudioGroupCreate.jsx": __vite_glob_0_107, - "./Pages/Studio/StudioGroupDashboard.jsx": __vite_glob_0_108, - "./Pages/Studio/StudioGroupEventEditor.jsx": __vite_glob_0_109, - "./Pages/Studio/StudioGroupEvents.jsx": __vite_glob_0_110, - "./Pages/Studio/StudioGroupInvitations.jsx": __vite_glob_0_111, - "./Pages/Studio/StudioGroupJoinRequests.jsx": __vite_glob_0_112, - "./Pages/Studio/StudioGroupMembers.jsx": __vite_glob_0_113, - "./Pages/Studio/StudioGroupPostEditor.jsx": __vite_glob_0_114, - "./Pages/Studio/StudioGroupPosts.jsx": __vite_glob_0_115, - "./Pages/Studio/StudioGroupProjectEditor.jsx": __vite_glob_0_116, - "./Pages/Studio/StudioGroupProjects.jsx": __vite_glob_0_117, - "./Pages/Studio/StudioGroupRecruitment.jsx": __vite_glob_0_118, - "./Pages/Studio/StudioGroupReleaseEditor.jsx": __vite_glob_0_119, - "./Pages/Studio/StudioGroupReleases.jsx": __vite_glob_0_120, - "./Pages/Studio/StudioGroupReputation.jsx": __vite_glob_0_121, - "./Pages/Studio/StudioGroupReviewQueue.jsx": __vite_glob_0_122, - "./Pages/Studio/StudioGroupSettings.jsx": __vite_glob_0_123, - "./Pages/Studio/StudioGroupsIndex.jsx": __vite_glob_0_124, - "./Pages/Studio/StudioGrowth.jsx": __vite_glob_0_125, - "./Pages/Studio/StudioInbox.jsx": __vite_glob_0_126, - "./Pages/Studio/StudioNewsEditor.jsx": __vite_glob_0_127, - "./Pages/Studio/StudioNewsIndex.jsx": __vite_glob_0_128, - "./Pages/Studio/StudioNewsTaxonomies.jsx": __vite_glob_0_129, - "./Pages/Studio/StudioPreferences.jsx": __vite_glob_0_130, - "./Pages/Studio/StudioProfile.jsx": __vite_glob_0_131, - "./Pages/Studio/StudioScheduled.jsx": __vite_glob_0_132, - "./Pages/Studio/StudioSearch.jsx": __vite_glob_0_133, - "./Pages/Studio/StudioSettings.jsx": __vite_glob_0_134, - "./Pages/Studio/StudioStories.jsx": __vite_glob_0_135, - "./Pages/Studio/StudioUploadQueue.jsx": __vite_glob_0_136, - "./Pages/Studio/StudioWorldEditor.jsx": __vite_glob_0_137, - "./Pages/Studio/StudioWorldsIndex.jsx": __vite_glob_0_138, - "./Pages/Upload/Index.jsx": __vite_glob_0_139, - "./Pages/World/WorldIndex.jsx": __vite_glob_0_140, - "./Pages/World/WorldShow.jsx": __vite_glob_0_141 + "./Pages/Academy/CoursesIndex.jsx": __vite_glob_0_1, + "./Pages/Academy/CoursesShow.jsx": __vite_glob_0_2, + "./Pages/Academy/Index.jsx": __vite_glob_0_3, + "./Pages/Academy/List.jsx": __vite_glob_0_4, + "./Pages/Academy/Pricing.jsx": __vite_glob_0_5, + "./Pages/Academy/Show.jsx": __vite_glob_0_6, + "./Pages/Admin/Academy/CourseBuilder.jsx": __vite_glob_0_7, + "./Pages/Admin/Academy/CourseEditor.jsx": __vite_glob_0_8, + "./Pages/Admin/Academy/CrudForm.jsx": __vite_glob_0_9, + "./Pages/Admin/Academy/CrudIndex.jsx": __vite_glob_0_10, + "./Pages/Admin/Academy/Dashboard.jsx": __vite_glob_0_11, + "./Pages/Admin/Academy/LessonEditor.jsx": __vite_glob_0_12, + "./Pages/Admin/Academy/Submissions.jsx": __vite_glob_0_13, + "./Pages/Admin/AiBiography.jsx": __vite_glob_0_14, + "./Pages/Admin/Artworks.jsx": __vite_glob_0_15, + "./Pages/Admin/AuthAudit.jsx": __vite_glob_0_16, + "./Pages/Admin/DailyActivity.jsx": __vite_glob_0_17, + "./Pages/Admin/Dashboard.jsx": __vite_glob_0_18, + "./Pages/Admin/FeaturedArtworks.jsx": __vite_glob_0_19, + "./Pages/Admin/HomepageAnnouncements/Form.jsx": __vite_glob_0_20, + "./Pages/Admin/HomepageAnnouncements/Index.jsx": __vite_glob_0_21, + "./Pages/Admin/Settings.jsx": __vite_glob_0_22, + "./Pages/Admin/Stories.jsx": __vite_glob_0_23, + "./Pages/Admin/UploadQueue.jsx": __vite_glob_0_24, + "./Pages/Admin/UsernameQueue.jsx": __vite_glob_0_25, + "./Pages/Admin/Users/Index.jsx": __vite_glob_0_26, + "./Pages/Artwork/SimilarArtworksHeader.jsx": __vite_glob_0_27, + "./Pages/ArtworkPage.jsx": __vite_glob_0_28, + "./Pages/CategoriesPage.jsx": __vite_glob_0_29, + "./Pages/Collection/CollectionAnalytics.jsx": __vite_glob_0_30, + "./Pages/Collection/CollectionDashboard.jsx": __vite_glob_0_31, + "./Pages/Collection/CollectionFeaturedIndex.jsx": __vite_glob_0_32, + "./Pages/Collection/CollectionHistory.jsx": __vite_glob_0_33, + "./Pages/Collection/CollectionManage.jsx": __vite_glob_0_34, + "./Pages/Collection/CollectionSeriesShow.jsx": __vite_glob_0_35, + "./Pages/Collection/CollectionShow.jsx": __vite_glob_0_36, + "./Pages/Collection/CollectionStaffProgramming.jsx": __vite_glob_0_37, + "./Pages/Collection/CollectionStaffSurfaces.jsx": __vite_glob_0_38, + "./Pages/Collection/FeaturedArtworksAdmin.jsx": __vite_glob_0_39, + "./Pages/Collection/NovaCardsAdminIndex.jsx": __vite_glob_0_40, + "./Pages/Collection/NovaCardsAssetPackAdmin.jsx": __vite_glob_0_41, + "./Pages/Collection/NovaCardsChallengeAdmin.jsx": __vite_glob_0_42, + "./Pages/Collection/NovaCardsCollectionAdmin.jsx": __vite_glob_0_43, + "./Pages/Collection/NovaCardsTemplateAdmin.jsx": __vite_glob_0_44, + "./Pages/Collection/SavedCollections.jsx": __vite_glob_0_45, + "./Pages/Community/CommunityActivityPage.jsx": __vite_glob_0_46, + "./Pages/Community/LatestCommentsPage.jsx": __vite_glob_0_47, + "./Pages/Feed/FollowingFeed.jsx": __vite_glob_0_48, + "./Pages/Feed/HashtagFeed.jsx": __vite_glob_0_49, + "./Pages/Feed/SavedFeed.jsx": __vite_glob_0_50, + "./Pages/Feed/SearchFeed.jsx": __vite_glob_0_51, + "./Pages/Feed/TrendingFeed.jsx": __vite_glob_0_52, + "./Pages/Forum/ForumCategory.jsx": __vite_glob_0_53, + "./Pages/Forum/ForumEditPost.jsx": __vite_glob_0_54, + "./Pages/Forum/ForumIndex.jsx": __vite_glob_0_55, + "./Pages/Forum/ForumNewThread.jsx": __vite_glob_0_56, + "./Pages/Forum/ForumSection.jsx": __vite_glob_0_57, + "./Pages/Forum/ForumThread.jsx": __vite_glob_0_58, + "./Pages/Group/GroupChallengeShow.jsx": __vite_glob_0_59, + "./Pages/Group/GroupEventShow.jsx": __vite_glob_0_60, + "./Pages/Group/GroupFaqPage.jsx": __vite_glob_0_61, + "./Pages/Group/GroupHelpPage.jsx": __vite_glob_0_62, + "./Pages/Group/GroupIndex.jsx": __vite_glob_0_63, + "./Pages/Group/GroupPostShow.jsx": __vite_glob_0_64, + "./Pages/Group/GroupProjectShow.jsx": __vite_glob_0_65, + "./Pages/Group/GroupQuickstartPage.jsx": __vite_glob_0_66, + "./Pages/Group/GroupReleaseShow.jsx": __vite_glob_0_67, + "./Pages/Group/GroupShow.jsx": __vite_glob_0_68, + "./Pages/Help/AccountHelpPage.jsx": __vite_glob_0_69, + "./Pages/Help/AuthHelpPage.jsx": __vite_glob_0_70, + "./Pages/Help/CardsHelpPage.jsx": __vite_glob_0_71, + "./Pages/Help/HelpCenterPage.jsx": __vite_glob_0_72, + "./Pages/Help/ProfileHelpPage.jsx": __vite_glob_0_73, + "./Pages/Help/StudioHelpPage.jsx": __vite_glob_0_74, + "./Pages/Help/TroubleshootingHelpPage.jsx": __vite_glob_0_75, + "./Pages/Help/UploadHelpPage.jsx": __vite_glob_0_76, + "./Pages/Help/WorldsHelpPage.jsx": __vite_glob_0_77, + "./Pages/Leaderboard/LeaderboardPage.jsx": __vite_glob_0_78, + "./Pages/Messages/Index.jsx": __vite_glob_0_79, + "./Pages/Moderation/AiBiographyAdmin.jsx": __vite_glob_0_80, + "./Pages/Moderation/ArtworkMaturityQueue.jsx": __vite_glob_0_81, + "./Pages/News/NewsComments.jsx": __vite_glob_0_82, + "./Pages/News/NewsImagePreview.jsx": __vite_glob_0_83, + "./Pages/Profile/ProfileGallery.jsx": __vite_glob_0_84, + "./Pages/Profile/ProfileShow.jsx": __vite_glob_0_85, + "./Pages/Settings/ProfileEdit.jsx": __vite_glob_0_86, + "./Pages/Studio/StudioActivity.jsx": __vite_glob_0_87, + "./Pages/Studio/StudioAnalytics.jsx": __vite_glob_0_88, + "./Pages/Studio/StudioArchived.jsx": __vite_glob_0_89, + "./Pages/Studio/StudioArtworkAnalytics.jsx": __vite_glob_0_90, + "./Pages/Studio/StudioArtworkEdit.jsx": __vite_glob_0_91, + "./Pages/Studio/StudioArtworks.jsx": __vite_glob_0_92, + "./Pages/Studio/StudioAssets.jsx": __vite_glob_0_93, + "./Pages/Studio/StudioCalendar.jsx": __vite_glob_0_94, + "./Pages/Studio/StudioCardAnalytics.jsx": __vite_glob_0_95, + "./Pages/Studio/StudioCardEditor.jsx": __vite_glob_0_96, + "./Pages/Studio/StudioCardsIndex.jsx": __vite_glob_0_97, + "./Pages/Studio/StudioChallenges.jsx": __vite_glob_0_98, + "./Pages/Studio/StudioCollections.jsx": __vite_glob_0_99, + "./Pages/Studio/StudioComments.jsx": __vite_glob_0_100, + "./Pages/Studio/StudioContentIndex.jsx": __vite_glob_0_101, + "./Pages/Studio/StudioDashboard.jsx": __vite_glob_0_102, + "./Pages/Studio/StudioDrafts.jsx": __vite_glob_0_103, + "./Pages/Studio/StudioFeatured.jsx": __vite_glob_0_104, + "./Pages/Studio/StudioFollowers.jsx": __vite_glob_0_105, + "./Pages/Studio/StudioGroupActivity.jsx": __vite_glob_0_106, + "./Pages/Studio/StudioGroupArtworks.jsx": __vite_glob_0_107, + "./Pages/Studio/StudioGroupAssets.jsx": __vite_glob_0_108, + "./Pages/Studio/StudioGroupChallengeEditor.jsx": __vite_glob_0_109, + "./Pages/Studio/StudioGroupChallenges.jsx": __vite_glob_0_110, + "./Pages/Studio/StudioGroupCollections.jsx": __vite_glob_0_111, + "./Pages/Studio/StudioGroupCreate.jsx": __vite_glob_0_112, + "./Pages/Studio/StudioGroupDashboard.jsx": __vite_glob_0_113, + "./Pages/Studio/StudioGroupEventEditor.jsx": __vite_glob_0_114, + "./Pages/Studio/StudioGroupEvents.jsx": __vite_glob_0_115, + "./Pages/Studio/StudioGroupInvitations.jsx": __vite_glob_0_116, + "./Pages/Studio/StudioGroupJoinRequests.jsx": __vite_glob_0_117, + "./Pages/Studio/StudioGroupMembers.jsx": __vite_glob_0_118, + "./Pages/Studio/StudioGroupPostEditor.jsx": __vite_glob_0_119, + "./Pages/Studio/StudioGroupPosts.jsx": __vite_glob_0_120, + "./Pages/Studio/StudioGroupProjectEditor.jsx": __vite_glob_0_121, + "./Pages/Studio/StudioGroupProjects.jsx": __vite_glob_0_122, + "./Pages/Studio/StudioGroupRecruitment.jsx": __vite_glob_0_123, + "./Pages/Studio/StudioGroupReleaseEditor.jsx": __vite_glob_0_124, + "./Pages/Studio/StudioGroupReleases.jsx": __vite_glob_0_125, + "./Pages/Studio/StudioGroupReputation.jsx": __vite_glob_0_126, + "./Pages/Studio/StudioGroupReviewQueue.jsx": __vite_glob_0_127, + "./Pages/Studio/StudioGroupSettings.jsx": __vite_glob_0_128, + "./Pages/Studio/StudioGroupsIndex.jsx": __vite_glob_0_129, + "./Pages/Studio/StudioGrowth.jsx": __vite_glob_0_130, + "./Pages/Studio/StudioInbox.jsx": __vite_glob_0_131, + "./Pages/Studio/StudioNewsEditor.jsx": __vite_glob_0_132, + "./Pages/Studio/StudioNewsIndex.jsx": __vite_glob_0_133, + "./Pages/Studio/StudioNewsTaxonomies.jsx": __vite_glob_0_134, + "./Pages/Studio/StudioPreferences.jsx": __vite_glob_0_135, + "./Pages/Studio/StudioProfile.jsx": __vite_glob_0_136, + "./Pages/Studio/StudioScheduled.jsx": __vite_glob_0_137, + "./Pages/Studio/StudioSearch.jsx": __vite_glob_0_138, + "./Pages/Studio/StudioSettings.jsx": __vite_glob_0_139, + "./Pages/Studio/StudioStories.jsx": __vite_glob_0_140, + "./Pages/Studio/StudioUploadQueue.jsx": __vite_glob_0_141, + "./Pages/Studio/StudioWorldEditor.jsx": __vite_glob_0_142, + "./Pages/Studio/StudioWorldsIndex.jsx": __vite_glob_0_143, + "./Pages/Upload/Index.jsx": __vite_glob_0_144, + "./Pages/World/WorldIndex.jsx": __vite_glob_0_145, + "./Pages/World/WorldShow.jsx": __vite_glob_0_146 }); const ClientOnlyPlaceholder = () => null; d( - (page) => W({ + (page) => W$1({ page, render: ReactDOMServer.renderToString, resolve: (name2) => { diff --git a/composer.lock b/composer.lock index 7c86778f..01a1a132 100644 --- a/composer.lock +++ b/composer.lock @@ -121,16 +121,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.379.6", + "version": "3.380.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "587f0bafd28a7dc3395b0c55f93f2474657767e2" + "reference": "af23f62b555be3ab337de571b45ae28558b6daf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/587f0bafd28a7dc3395b0c55f93f2474657767e2", - "reference": "587f0bafd28a7dc3395b0c55f93f2474657767e2", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/af23f62b555be3ab337de571b45ae28558b6daf6", + "reference": "af23f62b555be3ab337de571b45ae28558b6daf6", "shasum": "" }, "require": { @@ -212,9 +212,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.379.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.380.2" }, - "time": "2026-04-23T18:11:11+00:00" + "time": "2026-05-06T18:28:56+00:00" }, { "name": "brick/math", @@ -1847,16 +1847,16 @@ }, { "name": "intervention/image", - "version": "3.11.7", + "version": "3.11.8", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb" + "reference": "cf04c8dd245697f701057c13d4bfe140d584e738" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/2159bcccff18f09d2a392679b81a82c5a003f9bb", - "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb", + "url": "https://api.github.com/repos/Intervention/image/zipball/cf04c8dd245697f701057c13d4bfe140d584e738", + "reference": "cf04c8dd245697f701057c13d4bfe140d584e738", "shasum": "" }, "require": { @@ -1869,7 +1869,7 @@ "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", "slevomat/coding-standard": "~8.0", - "squizlabs/php_codesniffer": "^3.8" + "squizlabs/php_codesniffer": "^4" }, "suggest": { "ext-exif": "Recommended to be able to read EXIF data properly." @@ -1903,7 +1903,7 @@ ], "support": { "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/3.11.7" + "source": "https://github.com/Intervention/image/tree/3.11.8" }, "funding": [ { @@ -1919,7 +1919,7 @@ "type": "ko_fi" } ], - "time": "2026-02-19T13:11:17+00:00" + "time": "2026-05-01T08:20:10+00:00" }, { "name": "intervention/image-laravel", @@ -2202,16 +2202,16 @@ }, { "name": "laravel/framework", - "version": "v12.57.0", + "version": "v12.58.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "63a6ced3db46582b3276e2d03770a6317a94d6e2" + "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/63a6ced3db46582b3276e2d03770a6317a94d6e2", - "reference": "63a6ced3db46582b3276e2d03770a6317a94d6e2", + "url": "https://api.github.com/repos/laravel/framework/zipball/6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", + "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", "shasum": "" }, "require": { @@ -2420,7 +2420,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-04-22T13:21:29+00:00" + "time": "2026-04-26T16:42:04+00:00" }, { "name": "laravel/horizon", @@ -2563,16 +2563,16 @@ }, { "name": "laravel/reverb", - "version": "v1.10.0", + "version": "v1.10.1", "source": { "type": "git", "url": "https://github.com/laravel/reverb.git", - "reference": "a9c2b24ba455d0b2c22bb2851c15ba1adcb75240" + "reference": "a96310ae8b844d4862b2188a3cd6e79434893a6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/reverb/zipball/a9c2b24ba455d0b2c22bb2851c15ba1adcb75240", - "reference": "a9c2b24ba455d0b2c22bb2851c15ba1adcb75240", + "url": "https://api.github.com/repos/laravel/reverb/zipball/a96310ae8b844d4862b2188a3cd6e79434893a6b", + "reference": "a96310ae8b844d4862b2188a3cd6e79434893a6b", "shasum": "" }, "require": { @@ -2636,9 +2636,9 @@ ], "support": { "issues": "https://github.com/laravel/reverb/issues", - "source": "https://github.com/laravel/reverb/tree/v1.10.0" + "source": "https://github.com/laravel/reverb/tree/v1.10.1" }, - "time": "2026-03-29T14:51:57+00:00" + "time": "2026-04-30T12:07:26+00:00" }, { "name": "laravel/scout", @@ -2778,16 +2778,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.12", + "version": "v2.0.13", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919" + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/a6abb4e54f6fcd3138120b9ad497f0bd146f9919", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", "shasum": "" }, "require": { @@ -2835,20 +2835,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-04-14T13:33:34+00:00" + "time": "2026-04-16T14:03:50+00:00" }, { "name": "laravel/socialite", - "version": "v5.26.1", + "version": "v5.27.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4" + "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", - "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", + "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", "shasum": "" }, "require": { @@ -2907,7 +2907,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-03-29T14:50:53+00:00" + "time": "2026-04-24T14:05:47+00:00" }, { "name": "laravel/tinker", @@ -4833,16 +4833,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.51", + "version": "3.0.52", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "d59c94077f9c9915abb51ddb52ce85188ece1748" + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748", - "reference": "d59c94077f9c9915abb51ddb52ce85188ece1748", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce", "shasum": "" }, "require": { @@ -4923,7 +4923,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.51" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52" }, "funding": [ { @@ -4939,7 +4939,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T01:33:53+00:00" + "time": "2026-04-27T07:02:15+00:00" }, { "name": "predis/predis", @@ -6345,16 +6345,16 @@ }, { "name": "sentry/sentry", - "version": "4.25.0", + "version": "4.27.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "bfee3381e1f6dea8a5f3a18adba6419fe81c5f54" + "reference": "1f0544cff8443ac1d25d6521487118e28381a1c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/bfee3381e1f6dea8a5f3a18adba6419fe81c5f54", - "reference": "bfee3381e1f6dea8a5f3a18adba6419fe81c5f54", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/1f0544cff8443ac1d25d6521487118e28381a1c2", + "reference": "1f0544cff8443ac1d25d6521487118e28381a1c2", "shasum": "" }, "require": { @@ -6371,6 +6371,7 @@ "raven/raven": "*" }, "require-dev": { + "carthage-software/mago": "^1.13.3", "friendsofphp/php-cs-fixer": "^3.4", "guzzlehttp/promises": "^2.0.3", "guzzlehttp/psr7": "^1.8.4|^2.1.1", @@ -6423,7 +6424,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.25.0" + "source": "https://github.com/getsentry/sentry-php/tree/4.27.0" }, "funding": [ { @@ -6435,20 +6436,20 @@ "type": "custom" } ], - "time": "2026-04-23T12:50:03+00:00" + "time": "2026-05-06T14:32:16+00:00" }, { "name": "sentry/sentry-laravel", - "version": "4.25.0", + "version": "4.25.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "6699c9465df6bee07d82279f7a209db941913456" + "reference": "67efbdd74a752fcc1038676986b055a4df7d5084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/6699c9465df6bee07d82279f7a209db941913456", - "reference": "6699c9465df6bee07d82279f7a209db941913456", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/67efbdd74a752fcc1038676986b055a4df7d5084", + "reference": "67efbdd74a752fcc1038676986b055a4df7d5084", "shasum": "" }, "require": { @@ -6514,7 +6515,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.25.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.25.1" }, "funding": [ { @@ -6526,7 +6527,7 @@ "type": "custom" } ], - "time": "2026-04-07T12:55:27+00:00" + "time": "2026-05-05T09:22:46+00:00" }, { "name": "socialiteproviders/discord", @@ -6731,16 +6732,16 @@ }, { "name": "symfony/console", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", "shasum": "" }, "require": { @@ -6805,7 +6806,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.8" + "source": "https://github.com/symfony/console/tree/v7.4.9" }, "funding": [ { @@ -6825,20 +6826,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:54:39+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/css-selector", - "version": "v8.0.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed" + "reference": "3665cfade90565430909b906394c73c8739e57d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed", - "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/3665cfade90565430909b906394c73c8739e57d0", + "reference": "3665cfade90565430909b906394c73c8739e57d0", "shasum": "" }, "require": { @@ -6874,7 +6875,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.0.8" + "source": "https://github.com/symfony/css-selector/tree/v8.0.9" }, "funding": [ { @@ -6894,20 +6895,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -6920,7 +6921,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6945,7 +6946,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -6956,12 +6957,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/error-handler", @@ -7047,16 +7052,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v8.0.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6" + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0c3c1a17604c4dbbec4b93fe162c538482096e1f", + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f", "shasum": "" }, "require": { @@ -7108,7 +7113,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.9" }, "funding": [ { @@ -7128,20 +7133,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -7155,7 +7160,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7188,7 +7193,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -7199,25 +7204,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/filesystem", - "version": "v8.0.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a" + "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/66b769ae743ce2d13e435528fbef4af03d623e5a", - "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", + "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", "shasum": "" }, "require": { @@ -7254,7 +7263,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.8" + "source": "https://github.com/symfony/filesystem/tree/v8.0.9" }, "funding": [ { @@ -7274,7 +7283,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/finder", @@ -7428,16 +7437,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "017e76ad089bac281553389269e259e155935e1a" + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/017e76ad089bac281553389269e259e155935e1a", - "reference": "017e76ad089bac281553389269e259e155935e1a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/23486f59234c6fd6e8f1bec97124f3829d686627", + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627", "shasum": "" }, "require": { @@ -7523,7 +7532,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.10" }, "funding": [ { @@ -7543,7 +7552,7 @@ "type": "tidelift" } ], - "time": "2026-03-31T20:57:01+00:00" + "time": "2026-05-06T12:07:34+00:00" }, { "name": "symfony/mailer", @@ -7631,16 +7640,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379" + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6df02f99998081032da3407a8d6c4e1dcb5d4379", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379", + "url": "https://api.github.com/repos/symfony/mime/zipball/2d550c4758ba4c47519a6667c36553d535705b0c", + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c", "shasum": "" }, "require": { @@ -7696,7 +7705,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.8" + "source": "https://github.com/symfony/mime/tree/v7.4.9" }, "funding": [ { @@ -7716,7 +7725,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T14:11:46+00:00" + "time": "2026-04-29T13:21:53+00:00" }, { "name": "symfony/options-resolver", @@ -7791,7 +7800,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -7850,7 +7859,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -7874,16 +7883,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -7932,7 +7941,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -7952,11 +7961,11 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -8019,7 +8028,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" }, "funding": [ { @@ -8043,7 +8052,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -8104,7 +8113,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -8128,7 +8137,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -8189,7 +8198,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" }, "funding": [ { @@ -8213,7 +8222,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -8273,7 +8282,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -8297,7 +8306,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -8353,7 +8362,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0" }, "funding": [ { @@ -8377,7 +8386,7 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -8433,7 +8442,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" }, "funding": [ { @@ -8457,7 +8466,7 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", @@ -8513,7 +8522,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" }, "funding": [ { @@ -8537,16 +8546,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "2c408a6bb0313e6001a83628dc5506100474254e" + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/2c408a6bb0313e6001a83628dc5506100474254e", - "reference": "2c408a6bb0313e6001a83628dc5506100474254e", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", "shasum": "" }, "require": { @@ -8593,7 +8602,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" }, "funding": [ { @@ -8613,11 +8622,11 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:50:15+00:00" + "time": "2026-04-26T13:10:57+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -8676,7 +8685,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -8852,16 +8861,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b" + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", + "url": "https://api.github.com/repos/symfony/routing/zipball/287771d8bc86eacb30678dd10eda6c64a859951f", + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f", "shasum": "" }, "require": { @@ -8913,7 +8922,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.8" + "source": "https://github.com/symfony/routing/tree/v7.4.9" }, "funding": [ { @@ -8933,20 +8942,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -8964,7 +8973,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -9000,7 +9009,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -9020,7 +9029,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", @@ -9114,16 +9123,16 @@ }, { "name": "symfony/translation", - "version": "v8.0.8", + "version": "v8.0.10", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f" + "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f", - "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f", + "url": "https://api.github.com/repos/symfony/translation/zipball/f63e9342e12646a57c91ef8a366a4f9d8e557b67", + "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67", "shasum": "" }, "require": { @@ -9183,7 +9192,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v8.0.8" + "source": "https://github.com/symfony/translation/tree/v8.0.10" }, "funding": [ { @@ -9203,20 +9212,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-06T11:30:54+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", "shasum": "" }, "require": { @@ -9229,7 +9238,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -9265,7 +9274,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" }, "funding": [ { @@ -9285,20 +9294,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/uid", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56" + "reference": "2676b524340abcfe4d6151ec698463cebafee439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/6883ebdf7bf6a12b37519dbc0df62b0222401b56", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56", + "url": "https://api.github.com/repos/symfony/uid/zipball/2676b524340abcfe4d6151ec698463cebafee439", + "reference": "2676b524340abcfe4d6151ec698463cebafee439", "shasum": "" }, "require": { @@ -9343,7 +9352,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.8" + "source": "https://github.com/symfony/uid/tree/v7.4.9" }, "funding": [ { @@ -9363,7 +9372,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-30T15:19:22+00:00" }, { "name": "symfony/var-dumper", @@ -9593,16 +9602,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb" + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/d870a33f0f79d2b4579740b0620200221ee44aeb", - "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", "shasum": "" }, "require": { @@ -9639,7 +9648,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.1.0" + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { @@ -9663,7 +9672,7 @@ "type": "tidelift" } ], - "time": "2026-04-16T23:10:39+00:00" + "time": "2026-04-26T05:33:54+00:00" }, { "name": "yajra/laravel-datatables-oracle", @@ -9849,6 +9858,151 @@ ], "time": "2026-03-29T15:46:14+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, { "name": "doctrine/deprecations", "version": "1.1.6", @@ -10247,16 +10401,16 @@ }, { "name": "laravel/boost", - "version": "v2.4.5", + "version": "v2.4.6", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "60386c7723ff7cb388b62b6c137597244a9cf2f2" + "reference": "c9ea6368c66f7c0e6a9b26706b401de900cdb9ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/60386c7723ff7cb388b62b6c137597244a9cf2f2", - "reference": "60386c7723ff7cb388b62b6c137597244a9cf2f2", + "url": "https://api.github.com/repos/laravel/boost/zipball/c9ea6368c66f7c0e6a9b26706b401de900cdb9ac", + "reference": "c9ea6368c66f7c0e6a9b26706b401de900cdb9ac", "shasum": "" }, "require": { @@ -10309,7 +10463,7 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2026-04-22T13:29:20+00:00" + "time": "2026-04-28T11:52:01+00:00" }, { "name": "laravel/breeze", @@ -10656,16 +10810,16 @@ }, { "name": "laravel/sail", - "version": "v1.57.0", + "version": "v1.58.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "fa8d057b6e9310380ccbc3a209ed7f927d54f648" + "reference": "2e5e968138ca52ed87d712449697a8364d73b466" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/fa8d057b6e9310380ccbc3a209ed7f927d54f648", - "reference": "fa8d057b6e9310380ccbc3a209ed7f927d54f648", + "url": "https://api.github.com/repos/laravel/sail/zipball/2e5e968138ca52ed87d712449697a8364d73b466", + "reference": "2e5e968138ca52ed87d712449697a8364d73b466", "shasum": "" }, "require": { @@ -10715,7 +10869,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-04-14T13:32:04+00:00" + "time": "2026-04-27T13:38:34+00:00" }, { "name": "mockery/mockery", @@ -10958,38 +11112,39 @@ }, { "name": "pestphp/pest", - "version": "v4.6.3", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "bff44562a99d30aa37573995566051b0344f9f8e" + "reference": "2fc75cfcf03c041c804778fa894282234adc3c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/bff44562a99d30aa37573995566051b0344f9f8e", - "reference": "bff44562a99d30aa37573995566051b0344f9f8e", + "url": "https://api.github.com/repos/pestphp/pest/zipball/2fc75cfcf03c041c804778fa894282234adc3c66", + "reference": "2fc75cfcf03c041c804778fa894282234adc3c66", "shasum": "" }, "require": { "brianium/paratest": "^7.20.0", - "nunomaduro/collision": "^8.9.3", + "composer/xdebug-handler": "^3.0.5", + "nunomaduro/collision": "^8.9.4", "nunomaduro/termwind": "^2.4.0", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.2", "pestphp/pest-plugin-mutate": "^4.0.1", "pestphp/pest-plugin-profanity": "^4.2.1", "php": "^8.3.0", - "phpunit/phpunit": "^12.5.23", + "phpunit/phpunit": "^12.5.24", "symfony/process": "^7.4.8|^8.0.8" }, "conflict": { "filp/whoops": "<2.18.3", - "phpunit/phpunit": ">12.5.23", + "phpunit/phpunit": ">12.5.24", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "mrpunyapal/peststan": "^0.2.5", + "mrpunyapal/peststan": "^0.2.9", "pestphp/pest-dev-tools": "^4.1.0", "pestphp/pest-plugin-browser": "^4.3.1", "pestphp/pest-plugin-type-coverage": "^4.0.4", @@ -11020,6 +11175,7 @@ "Pest\\Plugins\\Verbose", "Pest\\Plugins\\Version", "Pest\\Plugins\\Shard", + "Pest\\Plugins\\Tia", "Pest\\Plugins\\Parallel" ] }, @@ -11059,7 +11215,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v4.6.3" + "source": "https://github.com/pestphp/pest/tree/v4.7.0" }, "funding": [ { @@ -11071,7 +11227,7 @@ "type": "github" } ], - "time": "2026-04-18T13:51:25+00:00" + "time": "2026-05-03T16:09:32+00:00" }, { "name": "pestphp/pest-plugin", @@ -11539,16 +11695,16 @@ }, { "name": "php-debugbar/php-debugbar", - "version": "v3.7.5", + "version": "v3.7.6", "source": { "type": "git", "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "dbf77f48fa6e6b57ed57ae67aa047b2535697788" + "reference": "1690ee1728827f9deb4b60457fa387cf44672c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/dbf77f48fa6e6b57ed57ae67aa047b2535697788", - "reference": "dbf77f48fa6e6b57ed57ae67aa047b2535697788", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/1690ee1728827f9deb4b60457fa387cf44672c56", + "reference": "1690ee1728827f9deb4b60457fa387cf44672c56", "shasum": "" }, "require": { @@ -11625,7 +11781,7 @@ ], "support": { "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v3.7.5" + "source": "https://github.com/php-debugbar/php-debugbar/tree/v3.7.6" }, "funding": [ { @@ -11637,7 +11793,7 @@ "type": "github" } ], - "time": "2026-04-15T11:58:43+00:00" + "time": "2026-04-30T07:31:44+00:00" }, { "name": "php-debugbar/symfony-bridge", @@ -12275,16 +12431,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.23", + "version": "12.5.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969" + "reference": "d75dd30597caa80e72fad2ef7904601a30ef1046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", - "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d75dd30597caa80e72fad2ef7904601a30ef1046", + "reference": "d75dd30597caa80e72fad2ef7904601a30ef1046", "shasum": "" }, "require": { @@ -12353,7 +12509,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.23" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.24" }, "funding": [ { @@ -12361,7 +12517,7 @@ "type": "other" } ], - "time": "2026-04-18T06:12:49+00:00" + "time": "2026-05-01T04:21:04+00:00" }, { "name": "sebastian/cli-parser", @@ -13314,16 +13470,16 @@ }, { "name": "symfony/yaml", - "version": "v8.0.8", + "version": "v8.0.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1" + "reference": "aa9ee60c41d9b20a2468c41ff0a32e2a7405ac05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1", - "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/aa9ee60c41d9b20a2468c41ff0a32e2a7405ac05", + "reference": "aa9ee60c41d9b20a2468c41ff0a32e2a7405ac05", "shasum": "" }, "require": { @@ -13365,7 +13521,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.0.8" + "source": "https://github.com/symfony/yaml/tree/v8.0.10" }, "funding": [ { @@ -13385,7 +13541,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-05T08:10:04+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", diff --git a/config/academy.php b/config/academy.php index a8a863d7..028637f3 100644 --- a/config/academy.php +++ b/config/academy.php @@ -21,4 +21,24 @@ return [ 'advanced', 'pro', ], + 'prompt_comparison' => [ + 'providers' => [ + 'ChatGPT', + 'Gemini', + 'Leonardo', + 'Bing', + 'Midjourney', + 'Flux', + 'Stable Diffusion', + ], + 'models' => [ + '4o Image', + 'Gemini Image', + 'Phoenix', + 'Designer', + 'V7', + 'Flux Pro', + 'SDXL', + ], + ], ]; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000001_add_content_markdown_to_academy_lessons_table.php b/database/migrations/2026_05_07_000001_add_content_markdown_to_academy_lessons_table.php new file mode 100644 index 00000000..b9bff185 --- /dev/null +++ b/database/migrations/2026_05_07_000001_add_content_markdown_to_academy_lessons_table.php @@ -0,0 +1,22 @@ +longText('content_markdown')->nullable()->after('content'); + }); + } + + public function down(): void + { + Schema::table('academy_lessons', function (Blueprint $table): void { + $table->dropColumn('content_markdown'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000002_add_course_fields_to_academy_lessons_table.php b/database/migrations/2026_05_07_000002_add_course_fields_to_academy_lessons_table.php new file mode 100644 index 00000000..3b13d1be --- /dev/null +++ b/database/migrations/2026_05_07_000002_add_course_fields_to_academy_lessons_table.php @@ -0,0 +1,29 @@ +unsignedInteger('lesson_number')->nullable()->after('slug'); + $table->unsignedInteger('course_order')->nullable()->after('lesson_number'); + $table->string('series_name', 120)->nullable()->after('course_order'); + + $table->index(['series_name', 'course_order', 'lesson_number'], 'academy_lessons_series_course_order_index'); + }); + } + + public function down(): void + { + Schema::table('academy_lessons', function (Blueprint $table): void { + $table->dropIndex('academy_lessons_series_course_order_index'); + $table->dropColumn(['lesson_number', 'course_order', 'series_name']); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000003_add_article_cover_image_to_academy_lessons_table.php b/database/migrations/2026_05_07_000003_add_article_cover_image_to_academy_lessons_table.php new file mode 100644 index 00000000..34238327 --- /dev/null +++ b/database/migrations/2026_05_07_000003_add_article_cover_image_to_academy_lessons_table.php @@ -0,0 +1,24 @@ +string('article_cover_image')->nullable()->after('cover_image'); + }); + } + + public function down(): void + { + Schema::table('academy_lessons', function (Blueprint $table): void { + $table->dropColumn('article_cover_image'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000004_add_tags_to_academy_lessons_table.php b/database/migrations/2026_05_07_000004_add_tags_to_academy_lessons_table.php new file mode 100644 index 00000000..edb608b3 --- /dev/null +++ b/database/migrations/2026_05_07_000004_add_tags_to_academy_lessons_table.php @@ -0,0 +1,24 @@ +json('tags')->nullable()->after('article_cover_image'); + }); + } + + public function down(): void + { + Schema::table('academy_lessons', function (Blueprint $table): void { + $table->dropColumn('tags'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000005_create_academy_courses_table.php b/database/migrations/2026_05_07_000005_create_academy_courses_table.php new file mode 100644 index 00000000..85a6c67c --- /dev/null +++ b/database/migrations/2026_05_07_000005_create_academy_courses_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->string('subtitle')->nullable(); + $table->text('excerpt')->nullable(); + $table->longText('description')->nullable(); + $table->string('cover_image')->nullable(); + $table->string('teaser_image')->nullable(); + $table->string('access_level')->default('free'); + $table->string('difficulty')->default('beginner'); + $table->string('status')->default('draft'); + $table->boolean('is_featured')->default(false); + $table->unsignedInteger('order_num')->default(0); + $table->unsignedInteger('estimated_minutes')->nullable(); + $table->unsignedInteger('lessons_count_cache')->default(0); + $table->timestamp('published_at')->nullable(); + $table->string('seo_title')->nullable(); + $table->text('seo_description')->nullable(); + $table->text('meta_keywords')->nullable(); + $table->string('og_title')->nullable(); + $table->text('og_description')->nullable(); + $table->string('og_image')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['status', 'published_at']); + $table->index(['is_featured', 'order_num']); + $table->index(['access_level', 'difficulty']); + }); + } + + public function down(): void + { + Schema::dropIfExists('academy_courses'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000006_create_academy_course_sections_table.php b/database/migrations/2026_05_07_000006_create_academy_course_sections_table.php new file mode 100644 index 00000000..d85abf66 --- /dev/null +++ b/database/migrations/2026_05_07_000006_create_academy_course_sections_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete(); + $table->string('title'); + $table->string('slug')->nullable(); + $table->text('description')->nullable(); + $table->unsignedInteger('order_num')->default(0); + $table->boolean('is_visible')->default(true); + $table->timestamps(); + + $table->index(['course_id', 'order_num']); + $table->unique(['course_id', 'slug']); + }); + } + + public function down(): void + { + Schema::dropIfExists('academy_course_sections'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000007_create_academy_course_lessons_table.php b/database/migrations/2026_05_07_000007_create_academy_course_lessons_table.php new file mode 100644 index 00000000..b44ccd25 --- /dev/null +++ b/database/migrations/2026_05_07_000007_create_academy_course_lessons_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete(); + $table->foreignId('section_id')->nullable()->constrained('academy_course_sections')->nullOnDelete(); + $table->foreignId('lesson_id')->constrained('academy_lessons')->cascadeOnDelete(); + $table->unsignedInteger('order_num')->default(0); + $table->boolean('is_required')->default(true); + $table->string('access_override')->nullable(); + $table->foreignId('unlock_after_lesson_id')->nullable()->constrained('academy_lessons')->nullOnDelete(); + $table->timestamps(); + + $table->unique(['course_id', 'lesson_id']); + $table->index(['course_id', 'section_id', 'order_num']); + $table->index(['lesson_id']); + $table->index(['is_required']); + }); + } + + public function down(): void + { + Schema::dropIfExists('academy_course_lessons'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_000008_create_academy_course_enrollments_table.php b/database/migrations/2026_05_07_000008_create_academy_course_enrollments_table.php new file mode 100644 index 00000000..96d95551 --- /dev/null +++ b/database/migrations/2026_05_07_000008_create_academy_course_enrollments_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete(); + $table->string('status')->default('active'); + $table->foreignId('last_lesson_id')->nullable()->constrained('academy_lessons')->nullOnDelete(); + $table->timestamp('started_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + + $table->unique(['user_id', 'course_id']); + $table->index(['course_id', 'status']); + $table->index(['user_id', 'status']); + }); + } + + public function down(): void + { + Schema::dropIfExists('academy_course_enrollments'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_07_180000_create_academy_lesson_revisions_table.php b/database/migrations/2026_05_07_180000_create_academy_lesson_revisions_table.php new file mode 100644 index 00000000..5a613b3d --- /dev/null +++ b/database/migrations/2026_05_07_180000_create_academy_lesson_revisions_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('lesson_id')->constrained('academy_lessons')->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); + $table->string('change_note', 255)->nullable(); + $table->json('snapshot_json'); + $table->timestamps(); + + $table->index(['lesson_id', 'created_at']); + }); + } + + public function down(): void + { + Schema::dropIfExists('academy_lesson_revisions'); + } +}; \ No newline at end of file diff --git a/database/seeders/AcademyDemoSeeder.php b/database/seeders/AcademyDemoSeeder.php index 07f981bd..68e0c175 100644 --- a/database/seeders/AcademyDemoSeeder.php +++ b/database/seeders/AcademyDemoSeeder.php @@ -28,8 +28,12 @@ class AcademyDemoSeeder extends Seeder 'slug' => 'what-is-ai-assisted-digital-art', 'category_slug' => 'prompting-basics', 'title' => 'What Is AI-Assisted Digital Art?', + 'lesson_number' => 1, + 'course_order' => 1, + 'series_name' => 'AI Art Basics', 'excerpt' => 'A grounded overview of how Skinbase creators can use AI as a creative assistant instead of a shortcut.', 'content' => 'AI-assisted digital art combines prompt-driven ideation, composition decisions, and manual finishing into a single workflow that still depends on taste and intent.', + 'tags' => ['ai art', 'basics', 'workflow'], 'difficulty' => 'beginner', 'access_level' => 'free', 'lesson_type' => 'article', @@ -39,19 +43,42 @@ class AcademyDemoSeeder extends Seeder 'slug' => 'prompting-basics-for-skinbase-creators', 'category_slug' => 'prompting-basics', 'title' => 'Prompting Basics for Skinbase Creators', + 'lesson_number' => 2, + 'course_order' => 2, + 'series_name' => 'AI Art Basics', 'excerpt' => 'Learn how to describe subject, mood, lighting, composition, and finish for Skinbase-native prompt workflows.', 'content' => 'Start with a clear subject. Add mood and lighting. Then anchor the composition for the output you actually want to upload.', + 'tags' => ['prompting', 'composition', 'lighting'], 'difficulty' => 'beginner', 'access_level' => 'free', 'lesson_type' => 'article', 'featured' => true, ], + [ + 'slug' => 'from-prompt-to-finished-artwork', + 'category_slug' => 'prompting-basics', + 'title' => 'From Prompt to Finished Artwork', + 'lesson_number' => 3, + 'course_order' => 3, + 'series_name' => 'AI Art Basics', + 'excerpt' => 'Move from raw generations to polished, upload-ready artwork with a clear finishing workflow.', + 'content' => 'Review the generation, choose the best candidate, clean up weak areas, and prepare the final upload with intentional metadata and presentation.', + 'tags' => ['editing', 'cleanup', 'publishing'], + 'difficulty' => 'beginner', + 'access_level' => 'free', + 'lesson_type' => 'workflow', + 'featured' => true, + ], [ 'slug' => 'ai-ethics-and-skinbase-upload-rules', 'category_slug' => 'ai-ethics', 'title' => 'AI Ethics and Skinbase Upload Rules', + 'lesson_number' => 7, + 'course_order' => 7, + 'series_name' => 'AI Art Basics', 'excerpt' => 'Use AI responsibly, label your workflow clearly, and avoid imitation-based prompt packs.', 'content' => 'Respect artists, disclose your workflow, and avoid packaging prompts around living-artist imitation or deceptive attribution.', + 'tags' => ['ethics', 'rules', 'publishing'], 'difficulty' => 'beginner', 'access_level' => 'free', 'lesson_type' => 'ethics', @@ -61,8 +88,12 @@ class AcademyDemoSeeder extends Seeder 'slug' => 'how-to-prepare-ai-artwork-for-upload', 'category_slug' => 'wallpapers', 'title' => 'How to Prepare AI Artwork for Upload', + 'lesson_number' => 8, + 'course_order' => 8, + 'series_name' => 'AI Art Basics', 'excerpt' => 'A cleanup checklist for exporting Academy-ready artwork to Skinbase.', 'content' => 'Check crop, upscale carefully, fix edge artifacts, and export a final image that fits the target category and composition.', + 'tags' => ['export', 'quality control', 'upload'], 'difficulty' => 'beginner', 'access_level' => 'free', 'lesson_type' => 'workflow', @@ -74,6 +105,7 @@ class AcademyDemoSeeder extends Seeder 'title' => 'Advanced Wallpaper Prompt Engineering', 'excerpt' => 'Build prompts that control focal depth, desktop negative space, and repeatable atmosphere.', 'content' => 'Use layered subject directives, desktop-safe composition notes, and finishing instructions to keep wallpapers clean and reusable.', + 'tags' => ['wallpapers', 'prompting', 'advanced'], 'difficulty' => 'advanced', 'access_level' => 'creator', 'lesson_type' => 'workflow', @@ -85,6 +117,7 @@ class AcademyDemoSeeder extends Seeder 'title' => 'Creating Consistent Robot Mascots', 'excerpt' => 'Keep mascot silhouettes, palettes, and expression readable across multiple prompt iterations.', 'content' => 'Define anchor traits first, then preserve them with repeated descriptors and a fixed framing recipe across generations.', + 'tags' => ['character design', 'consistency', 'iteration'], 'difficulty' => 'advanced', 'access_level' => 'creator', 'lesson_type' => 'workflow', @@ -96,6 +129,7 @@ class AcademyDemoSeeder extends Seeder 'title' => 'Building a Skinbase World From Scratch', 'excerpt' => 'Plan a world cover, atmosphere kit, and visual language that feels coherent beyond one image.', 'content' => 'Start from a world premise, define color anchors, then generate covers, teaser crops, and supporting scenes that share the same visual DNA.', + 'tags' => ['worldbuilding', 'visual language', 'series'], 'difficulty' => 'pro', 'access_level' => 'pro', 'lesson_type' => 'workflow', @@ -107,6 +141,7 @@ class AcademyDemoSeeder extends Seeder 'title' => 'Creating Editorial News Cover Images', 'excerpt' => 'Design news cover art that reads fast, supports headlines, and avoids clutter.', 'content' => 'Compose with headline space in mind, simplify the focal idea, and leave enough structure for editorial overlays to stay readable.', + 'tags' => ['covers', 'editorial', 'composition'], 'difficulty' => 'pro', 'access_level' => 'pro', 'lesson_type' => 'workflow', @@ -120,8 +155,12 @@ class AcademyDemoSeeder extends Seeder [ 'category_id' => $categories->get($lesson['category_slug'])?->id, 'title' => $lesson['title'], + 'lesson_number' => $lesson['lesson_number'] ?? null, + 'course_order' => $lesson['course_order'] ?? null, + 'series_name' => $lesson['series_name'] ?? null, 'excerpt' => $lesson['excerpt'], 'content' => $lesson['content'], + 'tags' => $lesson['tags'] ?? [], 'difficulty' => $lesson['difficulty'], 'access_level' => $lesson['access_level'], 'lesson_type' => $lesson['lesson_type'], @@ -161,6 +200,38 @@ class AcademyDemoSeeder extends Seeder } } + $plannedLessonUpdates = [ + 'writing-better-wallpaper-prompts' => [ + 'lesson_number' => 4, + 'course_order' => 4, + 'series_name' => 'AI Art Basics', + ], + 'style-mood-lighting-and-composition' => [ + 'lesson_number' => 5, + 'course_order' => 5, + 'series_name' => 'AI Art Basics', + ], + 'negative-prompts-and-quality-control' => [ + 'lesson_number' => 6, + 'course_order' => 6, + 'series_name' => 'AI Art Basics', + ], + 'titles-tags-categories-and-discovery' => [ + 'lesson_number' => 9, + 'course_order' => 9, + 'series_name' => 'AI Art Basics', + ], + 'from-idea-to-artwork-a-simple-skinbase-workflow' => [ + 'lesson_number' => 10, + 'course_order' => 10, + 'series_name' => 'AI Art Basics', + ], + ]; + + foreach ($plannedLessonUpdates as $slug => $attributes) { + AcademyLesson::query()->where('slug', $slug)->update($attributes); + } + $prompts = [ [ 'slug' => 'fantasy-floating-island-wallpaper', diff --git a/docs/ai-biography.md b/docs/ai-biography.md index 28b40f36..321e6d7a 100644 --- a/docs/ai-biography.md +++ b/docs/ai-biography.md @@ -233,7 +233,9 @@ Admin and maintenance commands print directly to the terminal. Admins can review biographies in cPad at: -- `/cp/ai-biography` +- `/moderation/ai-biography` + +The old `/cp/ai-biography` entry now redirects to the moderation surface. This surface shows stored records, review flags, failures, hidden states, and rebuild controls. @@ -396,12 +398,12 @@ It should not refresh just because of tiny download increments or ordering noise ### Admin cPad -- `GET /cp/ai-biography` -- `POST /cp/ai-biography/users/{user}/rebuild` -- `POST /cp/ai-biography/records/{biography}/approve` -- `POST /cp/ai-biography/records/{biography}/flag` -- `POST /cp/ai-biography/records/{biography}/hide` -- `POST /cp/ai-biography/records/{biography}/show` +- `GET /moderation/ai-biography` +- `POST /moderation/ai-biography/users/{user}/rebuild` +- `POST /moderation/ai-biography/records/{biography}/approve` +- `POST /moderation/ai-biography/records/{biography}/flag` +- `POST /moderation/ai-biography/records/{biography}/hide` +- `POST /moderation/ai-biography/records/{biography}/show` ## What the Public API Returns diff --git a/package-lock.json b/package-lock.json index aa928445..183152fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,11 +27,13 @@ "highlight.js": "^11.11.1", "laravel-echo": "^2.3.1", "lowlight": "^3.3.0", + "marked": "^18.0.3", "pusher-js": "^8.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-markdown": "^10.1.0", - "tippy.js": "^6.3.7" + "tippy.js": "^6.3.7", + "turndown": "^7.2.4" }, "devDependencies": { "@playwright/test": "^1.40.0", @@ -781,6 +783,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4592,6 +4600,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.3.tgz", + "integrity": "sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6815,6 +6835,19 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/turndown": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.4.tgz", + "integrity": "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + }, + "engines": { + "node": ">=18", + "npm": ">=9" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", diff --git a/package.json b/package.json index dd639fda..0b14f042 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,12 @@ "highlight.js": "^11.11.1", "laravel-echo": "^2.3.1", "lowlight": "^3.3.0", + "marked": "^18.0.3", "pusher-js": "^8.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-markdown": "^10.1.0", - "tippy.js": "^6.3.7" + "tippy.js": "^6.3.7", + "turndown": "^7.2.4" } } diff --git a/resources/css/app.css b/resources/css/app.css index d8e2e5fe..0f2c87e9 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1339,6 +1339,11 @@ text-wrap: pretty; } +.academy-lesson-prose blockquote p, +.academy-lesson-prose li p { + margin-bottom: 0; +} + .academy-lesson-prose p + p { margin-top: 0; } @@ -1420,6 +1425,54 @@ box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.08); } +.academy-lesson-prose li:has(> input[type='checkbox']), +.academy-lesson-prose li:has(> p input[type='checkbox']) { + padding-left: 0; +} + +.academy-lesson-prose li:has(> input[type='checkbox'])::before, +.academy-lesson-prose li:has(> p input[type='checkbox'])::before { + content: none; +} + +.academy-lesson-prose input[type='checkbox'] { + appearance: none; + -webkit-appearance: none; + display: inline-block; + width: 18px; + height: 18px; + min-width: 18px; + min-height: 18px; + margin: 0.1rem 0.7rem 0 0; + vertical-align: top; + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: 0.375rem; + background-color: rgba(255, 255, 255, 0.06); + background-position: center; + background-repeat: no-repeat; + background-size: 10px 10px; + box-sizing: border-box; + opacity: 1; + box-shadow: none; +} + +.academy-lesson-prose input[type='checkbox']:checked { + border-color: #E07A21; + background-color: #E07A21; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M1.5 6l3 3 6-6' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + box-shadow: 0 0 0 0 rgba(224, 122, 33, 0.45); +} + +.academy-lesson-prose input[type='checkbox']:disabled { + opacity: 1; + cursor: default; +} + +.academy-lesson-prose li > p:has(input[type='checkbox']) { + display: flex; + align-items: flex-start; +} + .academy-lesson-prose ol li::before { counter-increment: lesson-ordered-list; content: counter(lesson-ordered-list); @@ -1605,7 +1658,7 @@ align-items: flex-start; gap: 0.8rem; border-radius: 1rem; - padding: 0.8rem 0.9rem; + padding: 0.3rem 0.4rem; color: rgb(226 232 240 / 0.88); font-size: 0.96rem; line-height: 1.55; diff --git a/resources/js/Layouts/AdminLayout.jsx b/resources/js/Layouts/AdminLayout.jsx index 55b30288..df7436d4 100644 --- a/resources/js/Layouts/AdminLayout.jsx +++ b/resources/js/Layouts/AdminLayout.jsx @@ -34,6 +34,7 @@ const buildAdminNavGroups = (isAdmin) => [ label: 'Academy', items: [ { label: 'Academy Dashboard', href: '/moderation/academy/dashboard', icon: 'fa-solid fa-graduation-cap' }, + { label: 'Academy Courses', href: '/moderation/academy/courses', icon: 'fa-solid fa-road' }, { label: 'Academy Lessons', href: '/moderation/academy/lessons', icon: 'fa-solid fa-book-open' }, { label: 'Academy Prompts', href: '/moderation/academy/prompts', icon: 'fa-solid fa-wand-magic-sparkles' }, { label: 'Academy Challenges', href: '/moderation/academy/challenges', icon: 'fa-solid fa-trophy' }, diff --git a/resources/js/Pages/Academy/CoursesIndex.jsx b/resources/js/Pages/Academy/CoursesIndex.jsx new file mode 100644 index 00000000..9b7aa3d9 --- /dev/null +++ b/resources/js/Pages/Academy/CoursesIndex.jsx @@ -0,0 +1,125 @@ +import React from 'react' +import { Link, router, usePage } from '@inertiajs/react' +import SeoHead from '../../components/seo/SeoHead' +import NovaSelect from '../../components/ui/NovaSelect' + +function CourseCard({ course, variant = 'default' }) { + const isFeatured = variant === 'featured' + const progress = course?.progress || null + const cover = course?.cover_image_url || course?.teaser_image_url || course?.cover_image || course?.teaser_image || '' + + return ( + +
    + {cover ? :
    } +
    +
    + {course.difficulty} + {course.access_level} + {course.is_featured ? Featured : null} +
    +
    + +
    +

    {course.title}

    + {course.subtitle ?

    {course.subtitle}

    : null} +

    {course.excerpt || course.description || 'Structured Academy course.'}

    + +
    +
    +

    Lessons

    +

    {course.lessons_count || 0}

    +
    +
    +

    Duration

    +

    {course.estimated_minutes ? `${course.estimated_minutes} min` : 'Flexible'}

    +
    +
    +

    Progress

    +

    {progress ? `${progress.percent}%` : 'Start fresh'}

    +
    +
    +
    + + ) +} + +export default function AcademyCoursesIndex({ seo, title, description, items, featuredCourses = [], filters = {}, pricingUrl }) { + const flash = usePage().props.flash || {} + const difficultyOptions = [ + { value: '', label: 'All levels' }, + { value: 'beginner', label: 'Beginner' }, + { value: 'intermediate', label: 'Intermediate' }, + { value: 'advanced', label: 'Advanced' }, + ] + const accessOptions = [ + { value: '', label: 'All access' }, + { value: 'free', label: 'Free' }, + { value: 'premium', label: 'Premium' }, + { value: 'mixed', label: 'Mixed' }, + ] + + return ( +
    + + +
    +
    +
    +
    +

    Skinbase AI Academy

    +

    {title}

    +

    {description}

    +
    + See Academy plans +
    +
    + + {flash.success ?
    {flash.success}
    : null} + {flash.error ?
    {flash.error}
    : null} + + {featuredCourses.length ? ( +
    + +
    + {featuredCourses.slice(1, 3).map((course) => )} +
    +
    + ) : null} + +
    + router.get(window.location.pathname, { ...filters, difficulty: nextValue || undefined }, { preserveScroll: true, preserveState: true })} + options={difficultyOptions} + searchable={false} + className="rounded-2xl bg-white/[0.04]" + /> + router.get(window.location.pathname, { ...filters, access: nextValue || undefined }, { preserveScroll: true, preserveState: true })} + options={accessOptions} + searchable={false} + className="rounded-2xl bg-white/[0.04]" + /> +
    + + {(items?.data || []).length === 0 ? ( +
    No published Academy courses matched these filters.
    + ) : ( +
    + {items.data.map((course) => )} +
    + )} +
    +
    + ) +} \ No newline at end of file diff --git a/resources/js/Pages/Academy/CoursesShow.jsx b/resources/js/Pages/Academy/CoursesShow.jsx new file mode 100644 index 00000000..a296e1d9 --- /dev/null +++ b/resources/js/Pages/Academy/CoursesShow.jsx @@ -0,0 +1,339 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { Link, usePage } from '@inertiajs/react' +import SeoHead from '../../components/seo/SeoHead' + +function CourseBreadcrumbs({ items = [] }) { + if (!items.length) return null + + return ( + + ) +} + +function ProgressMeter({ progress }) { + const percent = Math.max(0, Math.min(100, Number(progress?.percent || 0))) + + return ( +
    +
    +
    +

    Progress

    +

    {percent}%

    +
    + + {progress ? 'In progress' : 'Not started'} + +
    +
    +
    +
    +

    + {progress + ? `${progress.completedRequired}/${progress.totalRequired} required lessons completed` + : 'Start the course to begin tracking progress through required lessons.'} +

    +
    + ) +} + +function LessonChip({ lesson }) { + const thumbnail = lesson?.cover_image_url || lesson?.article_cover_image_url || lesson?.cover_image || lesson?.article_cover_image || '' + const stepLabel = lesson?.course_step_label || null + const stepNumber = Number(lesson?.course_step_number || 0) + const isCompleted = Boolean(lesson?.completed) + const readingMinutes = Number(lesson?.reading_minutes || 0) + const ctaLabel = isCompleted ? 'Review lesson' : 'Open lesson' + + return ( + +
    + +
    +
    + {thumbnail ? ( + + ) : ( +
    + )} +
    +
    + {lesson.is_required ? ( + + Required + + ) : ( + + Optional + + )} + {isCompleted ? ( + + + Done + + ) : null} +
    +
    +
    + {stepLabel ?

    {stepLabel}

    : null} + {stepNumber > 0 ?

    {String(stepNumber).padStart(2, '0')}

    : null} +
    +
    +
    + +
    +
    +
    +
    + {stepLabel ?

    {stepLabel}

    : null} + {lesson.formatted_lesson_number ? {lesson.formatted_lesson_number} : null} + {lesson.difficulty || 'lesson'} + {lesson.access_level || 'free'} + {readingMinutes > 0 ? {readingMinutes} min : null} +
    +

    {lesson.title}

    +

    {isCompleted ? 'You already finished this lesson.' : 'Follow this step next in the course path.'}

    + +
    +

    {lesson.excerpt || lesson.content_preview || 'Open this lesson inside the course.'}

    +
    + +
    + {lesson.lesson_type || 'article'} + {lesson.category_name ? {lesson.category_name} : null} +
    +
    + +
    +
    +
    +

    Status

    +

    {isCompleted ? 'Completed' : 'Up next'}

    +
    +
    +

    Access

    +

    {lesson.access_level || 'Free'}

    +
    +
    +

    Read time

    +

    {readingMinutes > 0 ? `${readingMinutes} min` : 'Quick read'}

    +
    +
    + +
    + Continue path + + {ctaLabel} + + +
    +
    +
    +
    +
    + + ) +} + +function SectionBlock({ section, isActive = false }) { + if (!section?.is_visible) return null + + return ( +
    +
    +
    +
    +

    Course section

    + + {section.order_num + 1} + +
    +

    {section.title}

    + {section.description ?

    {section.description}

    : null} +
    +
    + {section.lessons?.length || 0} lessons + {isActive ? Reading now : null} +
    +
    + +
    + {(section.lessons || []).map((lesson) => ( + + ))} +
    +
    + ) +} + +export default function AcademyCoursesShow({ seo, course, sections = [], unsectionedLessons = [], pricingUrl }) { + const flash = usePage().props.flash || {} + const cover = course?.cover_image_url || course?.cover_image || course?.teaser_image_url || course?.teaser_image || '' + const progress = course?.progress || null + + const sectionJumpItems = useMemo( + () => [ + ...(unsectionedLessons.length ? [{ id: 'course-outline-core', label: 'Core lessons', count: unsectionedLessons.length }] : []), + ...sections + .filter((section) => section?.is_visible) + .map((section) => ({ id: `section-${section.id}`, label: section.title, count: (section.lessons || []).length })), + ], + [sections, unsectionedLessons], + ) + + const [activeJumpId, setActiveJumpId] = useState(sectionJumpItems[0]?.id || null) + + const breadcrumbs = [ + { label: 'Academy', href: '/academy' }, + { label: 'Courses', href: '/academy/courses' }, + { label: course?.title || 'Course', href: course?.public_url || '#' }, + ] + + useEffect(() => { + if (!sectionJumpItems.length || typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') { + return undefined + } + + const observer = new IntersectionObserver( + (entries) => { + const visibleEntries = entries.filter((entry) => entry.isIntersecting).sort((left, right) => right.intersectionRatio - left.intersectionRatio) + + if (!visibleEntries.length) return + + setActiveJumpId(visibleEntries[0].target.id) + }, + { + rootMargin: '-20% 0px -55% 0px', + threshold: [0.2, 0.45, 0.7], + }, + ) + + const elements = sectionJumpItems.map((item) => document.getElementById(item.id)).filter(Boolean) + elements.forEach((element) => observer.observe(element)) + + return () => observer.disconnect() + }, [sectionJumpItems]) + + return ( +
    + + +
    + {flash.success ?
    {flash.success}
    : null} + {flash.error ?
    {flash.error}
    : null} + +
    +
    +
    + {cover ? : null} +
    +
    + + +
    + Academy course + {course?.difficulty} + {course?.access_level} + {progress?.percent ? {progress.percent}% complete : null} +
    + +
    +

    {course?.title}

    + {course?.subtitle ?

    {course.subtitle}

    : null} +

    {course?.excerpt || course?.description}

    + +
    + {cover ? ( + + ) : ( +
    + No course cover image yet +
    + )} +
    +
    +
    +
    + + +
    +
    + +
    + {unsectionedLessons.length ? ( + + ) : null} + + {sections.filter((section) => section?.is_visible).map((section) => ( + + ))} +
    +
    +
    + ) +} diff --git a/resources/js/Pages/Academy/Index.jsx b/resources/js/Pages/Academy/Index.jsx index f8a65976..20ed3703 100644 --- a/resources/js/Pages/Academy/Index.jsx +++ b/resources/js/Pages/Academy/Index.jsx @@ -17,7 +17,29 @@ function FeatureCard({ title, description, href, cta }) { ) } -export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredLessons, featuredPrompts, featuredChallenges }) { +function FeaturedCourseCard({ course }) { + const cover = course?.cover_image_url || course?.teaser_image_url || course?.cover_image || course?.teaser_image || '' + + return ( + +
    + {cover ? : null} +
    +
    + {course.difficulty} + {course.access_level} +
    +
    +
    +

    {course.title}

    +

    {course.excerpt || course.description || 'Guided Academy course.'}

    +

    {course.lessons_count || 0} lessons · {course.estimated_minutes ? `${course.estimated_minutes} min` : 'Flexible duration'}

    +
    + + ) +} + +export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredCourses, featuredLessons, featuredPrompts, featuredChallenges }) { const jsonLd = [{ '@context': 'https://schema.org', '@type': 'WebPage', @@ -39,6 +61,7 @@ export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, sta

    Skinbase AI Academy is the creative learning hub for AI-assisted art on Skinbase. Start with free lessons, explore prompt templates, and unlock premium workflows later.

    + Browse courses Browse lessons Open prompt library See plans @@ -57,19 +80,35 @@ export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, sta
    + -
    -
    +
    +

    Courses

    {stats?.courseCount || 0}

    Lessons

    {stats?.lessonCount || 0}

    Prompts

    {stats?.promptCount || 0}

    Challenges

    {stats?.challengeCount || 0}

    + {featuredCourses?.length ? ( +
    +
    +
    +

    Featured courses

    +

    Guided Academy paths

    +
    + All courses +
    +
    + {featuredCourses.slice(0, 3).map((course) => )} +
    +
    + ) : null} +
    -

    Featured lessons

    {(featuredLessons || []).slice(0, 3).map((item) => {item.title})}
    +

    Featured lessons

    {(featuredLessons || []).slice(0, 3).map((item) => {item.lesson_label || 'Featured lesson'}{item.title})}

    Featured prompts

    {(featuredPrompts || []).slice(0, 3).map((item) => {item.title})}

    Current challenges

    {(featuredChallenges || []).slice(0, 3).map((item) => {item.title})}
    diff --git a/resources/js/Pages/Academy/List.jsx b/resources/js/Pages/Academy/List.jsx index dbec0160..d3278279 100644 --- a/resources/js/Pages/Academy/List.jsx +++ b/resources/js/Pages/Academy/List.jsx @@ -65,15 +65,125 @@ function itemHref(pageType, item) { return academyHref('challenges', item.slug) } +function PromptLibraryHero({ title, description, items, pricingUrl }) { + const featuredImages = (items || []) + .map((item) => item?.preview_image) + .filter(Boolean) + .slice(0, 3) + + const primaryImage = featuredImages[0] || '' + const supportingImages = featuredImages.slice(1, 3) + + return ( +
    +
    +
    +
    + Skinbase AI Academy + Prompt Library +
    +

    {title}

    +

    {description}

    + +
    +
    +

    Visual-first

    +

    Preview prompt results before opening the detail page.

    +
    +
    +

    Reusable

    +

    Templates for wallpapers, covers, worlds, portraits, and more.

    +
    +
    +

    Comparison-ready

    +

    See which prompts include provider-specific notes and outputs.

    +
    +
    + +
    + Upgrade preview + {items?.length || 0} prompts in view +
    +
    + +
    + {primaryImage ? ( + <> +
    + +
    + + {supportingImages.length ? ( +
    + {supportingImages.map((image, index) => ( +
    + +
    + ))} +
    + ) : null} + + ) : ( +
    + Prompt preview images will appear here +
    + )} +
    +
    +
    + ) +} + function AcademyCard({ pageType, item }) { + const lessonSeries = String(item?.series_name || '').trim() + const promptPreviewImage = item?.preview_image || '' + + if (pageType === 'prompts') { + return ( + +
    + {promptPreviewImage ? : null} +
    +
    + Prompt template + +
    +
    +
    + {item?.difficulty ? {item.difficulty} : null} + {item?.aspect_ratio ? {item.aspect_ratio} : null} +
    +
    +
    + +
    +
    +

    {item?.category?.name || 'Academy'}

    + {Array.isArray(item?.tool_notes) && item.tool_notes.length ? {item.tool_notes.length} comparisons : null} +
    +

    {item.title}

    +

    {item.excerpt || item.description || item.prompt_preview || 'No description yet.'}

    + {item.tags?.length ?

    {item.tags.slice(0, 4).join(' · ')}

    : null} +
    + + ) + } + return (

    {pageType.slice(0, -1)}

    + {pageType === 'lessons' && item?.formatted_lesson_number ? ( +
    + {item.formatted_lesson_number} + {lessonSeries ? {lessonSeries} : null} +
    + ) : null}

    {item.title}

    {item.excerpt || item.description || item.prompt_preview || item.content_preview || 'No description yet.'}

    + {pageType === 'lessons' && item.tags?.length ?

    {item.tags.slice(0, 4).join(' · ')}

    : null} {pageType === 'prompts' && item.tags?.length ?

    {item.tags.slice(0, 4).join(' · ')}

    : null} {pageType === 'challenges' ?

    {item.status} · {item.submission_count ?? 0} submissions

    : null} @@ -82,33 +192,36 @@ function AcademyCard({ pageType, item }) { export default function AcademyList({ pageType, title, description, seo, items, filters, categories, pricingUrl }) { const flash = usePage().props.flash || {} + const visibleItems = Array.isArray(items?.data) ? items.data : [] return (
    -
    -
    -
    -

    Skinbase AI Academy

    -

    {title}

    -

    {description}

    + {pageType === 'prompts' ? : ( +
    +
    +
    +

    Skinbase AI Academy

    +

    {title}

    +

    {description}

    +
    + Upgrade preview
    - Upgrade preview -
    -
    +
    + )} {flash.success ?
    {flash.success}
    : null} {flash.error ?
    {flash.error}
    : null} - {(items?.data || []).length === 0 ? ( + {visibleItems.length === 0 ? (
    Nothing matched this Academy view yet.
    ) : (
    - {items.data.map((item) => )} + {visibleItems.map((item) => )}
    )}
    diff --git a/resources/js/Pages/Academy/Show.jsx b/resources/js/Pages/Academy/Show.jsx index ed8af800..950dbaf0 100644 --- a/resources/js/Pages/Academy/Show.jsx +++ b/resources/js/Pages/Academy/Show.jsx @@ -2,6 +2,35 @@ import React, { useEffect, useRef, useState } from 'react' import { Link, router, usePage } from '@inertiajs/react' import SeoHead from '../../components/seo/SeoHead' +function academyHref(section, slug) { + return `/academy/${section}/${encodeURIComponent(slug)}` +} + +function AcademyBreadcrumbs({ items = [] }) { + if (!items.length) return null + + return ( + + ) +} + function slugifyHeading(value, fallback = 'section') { const normalized = String(value || '') .toLowerCase() @@ -48,6 +77,29 @@ function LessonInfoRow({ label, value }) { ) } +function LessonNavCard({ direction, lesson }) { + if (!lesson) return null + + const eyebrow = direction === 'previous' ? 'Previous lesson' : 'Next lesson' + const alignClass = direction === 'previous' ? 'items-start text-left' : 'items-end text-right' + const href = lesson.course_url || `/academy/lessons/${lesson.slug}` + + return ( + +
    +

    {eyebrow}

    + {lesson.lesson_label ?

    {lesson.lesson_label}

    : null} +

    {lesson.title}

    +
    +

    {lesson.excerpt || lesson.content_preview || 'Open the next step in this Academy sequence.'}

    + + ) +} + function LockedPanel({ pricingUrl, label }) { return (
    @@ -87,7 +139,7 @@ function copyTextToClipboard(text) { return Promise.reject(new Error('Clipboard unavailable')) } -function PromptCopyButton({ prompt }) { +function PromptCopyButton({ prompt, label = 'Copy prompt' }) { const [status, setStatus] = useState('idle') const resetTimerRef = useRef(0) @@ -107,11 +159,172 @@ function PromptCopyButton({ prompt }) { aria-label="Copy prompt" > - {status === 'copied' ? 'Copied' : status === 'failed' ? 'Copy failed' : 'Copy prompt'} + {status === 'copied' ? 'Copied' : status === 'failed' ? 'Copy failed' : label} ) } +function ImageLightbox({ gallery, onClose, onNavigate }) { + useEffect(() => { + if (!gallery?.images?.length) return undefined + + const handleEscape = (event) => { + if (event.key === 'Escape') { + onClose() + return + } + + if (event.key === 'ArrowLeft') { + onNavigate(-1) + return + } + + if (event.key === 'ArrowRight') { + onNavigate(1) + } + } + + document.body.style.overflow = 'hidden' + window.addEventListener('keydown', handleEscape) + + return () => { + document.body.style.overflow = '' + window.removeEventListener('keydown', handleEscape) + } + }, [gallery, onClose, onNavigate]) + + const images = Array.isArray(gallery?.images) ? gallery.images : [] + const currentIndex = Math.max(0, Math.min(images.length - 1, Number(gallery?.index || 0))) + const currentImage = images[currentIndex] + + if (!currentImage?.src) return null + + return ( +
    + + {images.length > 1 ? ( + + ) : null} + {images.length > 1 ? ( + + ) : null} +
    event.stopPropagation()}> + {currentImage.alt + {images.length > 1 ? ( +
    +
    +

    {currentImage.alt || `Image ${currentIndex + 1}`}

    +

    {`Image ${currentIndex + 1} of ${images.length}`}

    +
    +
    + {images.map((image, index) => ( +
    +
    + ) : null} +
    +
    + ) +} + +function PromptToolNoteCard({ note, index, galleryIndex, onOpenImage }) { + if (!note || typeof note !== 'object') return null + + const title = note.model_name || note.provider || `Comparison ${String(index + 1).padStart(2, '0')}` + const subtitle = [note.provider, note.model_name].filter(Boolean).join(' · ') + const previewUrl = note.image_url || note.thumb_url || '' + const hasContent = Boolean(note.notes || note.strengths || note.weaknesses || note.best_for || note.settings || previewUrl || note.score || subtitle) + + if (!hasContent) return null + + return ( +
    + {previewUrl ? ( + + ) : null} + +
    +
    +

    AI comparison

    +

    {title}

    + {subtitle ?

    {subtitle}

    : null} +
    +
    + {String(index + 1).padStart(2, '0')} + {note.score ? {`Score ${note.score}/10`} : null} +
    +
    + +
    + {note.settings ? ( +
    +

    Generated in

    +

    {note.settings}

    +
    + ) : null} + + {note.notes ? ( +
    +

    Overall notes

    +

    {note.notes}

    +
    + ) : null} + + {note.best_for ? ( +
    +

    Best for

    +

    {note.best_for}

    +
    + ) : null} + +
    + {note.strengths ? ( +
    +

    Strengths

    +

    {note.strengths}

    +
    + ) : null} + + {note.weaknesses ? ( +
    +

    Weaknesses

    +

    {note.weaknesses}

    +
    + ) : null} +
    +
    +
    + ) +} + function AiComparisonSection({ block }) { const payload = block?.payload || {} const criteria = Array.isArray(payload.criteria) ? payload.criteria.filter(Boolean) : [] @@ -227,42 +440,89 @@ function AiComparisonSection({ block }) { ) } -export default function AcademyShow({ pageType, item, relatedLessons = [], seo, pricingUrl, completeUrl, completed: initialCompleted, saveUrl, unsaveUrl, saved: initialSaved, submitUrl }) { +export default function AcademyShow({ pageType, item, relatedLessons = [], relatedCourses = [], previousLesson = null, nextLesson = null, seo, pricingUrl, completeUrl, completed: initialCompleted, saveUrl, unsaveUrl, saved: initialSaved, submitUrl, courseContext = null }) { const flash = usePage().props.flash || {} const [completed, setCompleted] = useState(Boolean(initialCompleted)) const [saved, setSaved] = useState(Boolean(initialSaved)) const [tableOfContents, setTableOfContents] = useState([]) const [activeHeadingId, setActiveHeadingId] = useState('') + const [lightboxGallery, setLightboxGallery] = useState(null) const articleContentRef = useRef(null) + const handledInitialHashRef = useRef(false) const lessonCover = item?.cover_image_url || item?.cover_image || '' + const articleCover = item?.article_cover_image_url || item?.article_cover_image || '' const lessonCategory = item?.category?.name || 'Academy' + const lessonSeries = String(item?.series_name || '').trim() || lessonCategory const lessonDifficulty = item?.difficulty || 'Intermediate' const lessonMinutes = formatLessonMinutes(item?.reading_minutes) const lessonUpdated = formatLessonDate(item?.published_at) const lessonBlocks = Array.isArray(item?.blocks) ? item.blocks : [] const relatedLessonList = Array.isArray(relatedLessons) ? relatedLessons : [] + const relatedCourseList = Array.isArray(relatedCourses) ? relatedCourses : [] + const courseOutline = Array.isArray(courseContext?.outline) ? courseContext.outline : [] const lessonSummary = item.excerpt || item.description || item.prompt_preview || item.content_preview || 'A focused Academy lesson with practical guidance and examples.' + const lessonTags = Array.isArray(item?.tags) ? item.tags.filter(Boolean) : [] + const promptPreviewImage = item?.preview_image || '' + const promptBody = item?.prompt || item?.prompt_preview || '' + const promptComparisons = Array.isArray(item?.tool_notes) + ? item.tool_notes.filter((note) => note && typeof note === 'object' && note.active !== false && [ + note.provider, + note.model_name, + note.notes, + note.strengths, + note.weaknesses, + note.best_for, + note.image_path, + note.image_url, + note.thumb_path, + note.thumb_url, + note.settings, + note.score, + ].some(Boolean)) + : [] + const promptUsageNotes = String(item?.usage_notes || '').trim() + const promptWorkflowNotes = String(item?.workflow_notes || '').trim() + const promptHasFullAccess = Boolean(item?.prompt) + const promptModelsCovered = promptComparisons.map((note, index) => note.model_name || note.provider || `Model ${index + 1}`) + const promptComparisonGalleryImages = promptComparisons + .map((note, index) => { + const src = note.image_url || note.thumb_url || '' + if (!src) return null + + return { + src, + alt: note.model_name || note.provider || `Comparison ${index + 1}`, + } + }) + .filter(Boolean) + const academyBreadcrumbs = pageType === 'prompt' + ? [ + { label: 'Academy', href: '/academy' }, + { label: 'Prompt Library', href: '/academy/prompts' }, + { label: item?.title || 'Prompt' }, + ] + : [] const fontScaleStorageKey = 'academy.lesson.font-scale' const fontScaleMin = 0.95 const fontScaleMax = 1.12 const fontScaleStep = 0.04 - const [lessonFontScale, setLessonFontScale] = useState(() => { - if (typeof window === 'undefined') { - return 1.04 + const [lessonFontScale, setLessonFontScale] = useState(1.04) + + const findArticleHeading = (headingId) => { + if (!headingId || typeof document === 'undefined') { + return null } - const storedValue = Number(window.localStorage.getItem(fontScaleStorageKey)) + const escapedHeadingId = typeof CSS !== 'undefined' && typeof CSS.escape === 'function' + ? CSS.escape(headingId) + : String(headingId).replace(/[^a-zA-Z0-9_-]/g, '') - if (Number.isFinite(storedValue)) { - return Math.min(fontScaleMax, Math.max(fontScaleMin, storedValue)) - } - - return 1.04 - }) + return articleContentRef.current?.querySelector(`#${escapedHeadingId}`) || document.getElementById(headingId) + } const markComplete = () => { if (!completeUrl || completed) return - router.post(completeUrl, {}, { + router.post(completeUrl, courseContext?.completePayload || {}, { preserveScroll: true, onSuccess: () => setCompleted(true), }) @@ -285,6 +545,64 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, setLessonFontScale((current) => Math.min(fontScaleMax, Number((current + fontScaleStep).toFixed(2)))) } + const openPromptPreviewImage = () => { + if (!promptPreviewImage) return + + setLightboxGallery({ + images: [{ src: promptPreviewImage, alt: item?.title || 'Prompt preview' }], + index: 0, + }) + } + + const openPromptComparisonGallery = (index) => { + if (!promptComparisonGalleryImages.length) return + + setLightboxGallery({ + images: promptComparisonGalleryImages, + index: Math.max(0, Math.min(promptComparisonGalleryImages.length - 1, Number(index || 0))), + }) + } + + const navigateLightboxGallery = (direction) => { + setLightboxGallery((current) => { + if (!current?.images?.length) return current + + const total = current.images.length + const nextIndex = typeof direction === 'number' && Math.abs(direction) > 1 + ? Math.max(0, Math.min(total - 1, current.index + direction)) + : (current.index + direction + total) % total + + return { + ...current, + index: nextIndex, + } + }) + } + + const scrollToHeading = (headingId, behavior = 'smooth') => { + if (typeof window === 'undefined') { + return + } + + const heading = findArticleHeading(headingId) + + if (!heading) { + return + } + + const top = Math.max(0, window.scrollY + heading.getBoundingClientRect().top - 112) + window.scrollTo({ top, behavior }) + setActiveHeadingId(headingId) + + if (window.history?.replaceState) { + window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${headingId}`) + } + } + + useEffect(() => { + handledInitialHashRef.current = false + }, [item?.slug]) + useEffect(() => { if (pageType !== 'lesson' || !item?.content || !articleContentRef.current) { setTableOfContents([]) @@ -301,6 +619,7 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, seenIds.set(baseId, seenCount + 1) heading.id = nextId + heading.style.scrollMarginTop = '128px' return { id: nextId, @@ -312,42 +631,98 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, setTableOfContents(nextTableOfContents) }, [item?.content, pageType]) + useEffect(() => { + if (pageType !== 'lesson' || tableOfContents.length === 0 || handledInitialHashRef.current || typeof window === 'undefined') { + return + } + + const hash = window.location.hash.replace(/^#/, '').trim() + + if (!hash) { + handledInitialHashRef.current = true + return + } + + const matchingEntry = tableOfContents.find((entry) => entry.id === hash) + + if (!matchingEntry) { + handledInitialHashRef.current = true + return + } + + handledInitialHashRef.current = true + window.requestAnimationFrame(() => scrollToHeading(matchingEntry.id, 'auto')) + }, [pageType, tableOfContents]) + + useEffect(() => { + if (pageType !== 'lesson' || tableOfContents.length === 0 || typeof window === 'undefined') { + return undefined + } + + const handleHashChange = () => { + const hash = window.location.hash.replace(/^#/, '').trim() + + if (!hash) { + return + } + + const matchingEntry = tableOfContents.find((entry) => entry.id === hash) + + if (!matchingEntry) { + return + } + + window.requestAnimationFrame(() => scrollToHeading(matchingEntry.id, 'auto')) + } + + window.addEventListener('hashchange', handleHashChange) + return () => window.removeEventListener('hashchange', handleHashChange) + }, [pageType, tableOfContents]) + useEffect(() => { if (pageType !== 'lesson' || tableOfContents.length === 0 || !articleContentRef.current) { setActiveHeadingId('') return } - const headingElements = Array.from(articleContentRef.current.querySelectorAll('h2, h3')) + const getActiveId = () => { + const headings = Array.from(articleContentRef.current.querySelectorAll('h2[id], h3[id]')) + if (!headings.length) return '' - if (!headingElements.length) { - setActiveHeadingId('') + // offset accounts for sticky header height + small buffer + const offset = 140 + let activeId = headings[0].id + + for (const heading of headings) { + if (heading.getBoundingClientRect().top <= offset) { + activeId = heading.id + } + } + + return activeId + } + + setActiveHeadingId(getActiveId()) + + const onScroll = () => setActiveHeadingId(getActiveId()) + window.addEventListener('scroll', onScroll, { passive: true }) + + return () => window.removeEventListener('scroll', onScroll) + }, [pageType, tableOfContents, lessonFontScale]) + + useEffect(() => { + if (typeof window === 'undefined') { return } - const observer = new IntersectionObserver((entries) => { - const visibleEntries = entries - .filter((entry) => entry.isIntersecting) - .sort((left, right) => left.boundingClientRect.top - right.boundingClientRect.top) + const storedValue = Number(window.localStorage.getItem(fontScaleStorageKey)) - if (visibleEntries.length) { - setActiveHeadingId((current) => visibleEntries[0].target.id || current) - } - }, { - root: null, - rootMargin: '-18% 0px -68% 0px', - threshold: [0, 1], - }) - - headingElements.forEach((heading) => observer.observe(heading)) - - const firstVisibleHeading = headingElements.find((heading) => heading.getBoundingClientRect().top >= 0) || headingElements[0] - if (firstVisibleHeading?.id) { - setActiveHeadingId(firstVisibleHeading.id) + if (!Number.isFinite(storedValue)) { + return } - return () => observer.disconnect() - }, [pageType, tableOfContents, lessonFontScale]) + setLessonFontScale(Math.min(fontScaleMax, Math.max(fontScaleMin, storedValue))) + }, [fontScaleMax, fontScaleMin, fontScaleStorageKey]) useEffect(() => { if (typeof window === 'undefined') { @@ -452,7 +827,7 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, }, [item?.content, lessonFontScale, pageType]) return ( -
    +
    @@ -475,9 +850,27 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, {lessonDifficulty}
    + {item.lesson_label ?

    {item.lesson_label}

    : null} +

    {item.title}

    {lessonSummary}

    + {lessonTags.length ? ( +
    + {lessonTags.map((tag) => ( + {tag} + ))} +
    + ) : null} + + {courseContext?.title ? ( +
    +

    Part of course

    + {courseContext.title} +

    {courseContext.subtitle || 'This lesson is being viewed inside a structured Academy course path.'}

    +
    + ) : null} +
    {completeUrl ? : null} {saveUrl ? : null} @@ -488,7 +881,7 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo, - +
    @@ -500,7 +893,8 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo,
    - + + {item.formatted_lesson_number ? : null} @@ -508,7 +902,7 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo,

    Lesson status

    -

    {item.locked ? 'This lesson is partially locked for your account level.' : 'Full lesson content is available below.'}

    +

    {item.locked ? 'This lesson is partially locked for your account level.' : courseContext?.title ? 'This lesson is being tracked inside a course. Completion updates your course progress.' : 'Full lesson content is available below.'}

    @@ -516,7 +910,7 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo,
    -
    +

    Article

    @@ -549,6 +943,12 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo,
    + {articleCover ? ( +
    + {`${item.title} +
    + ) : null} + {item.content ? (
    )}
    + + {(previousLesson || nextLesson) ? ( +
    +

    {courseContext?.title ? 'Course navigation' : 'Lesson navigation'}

    +

    {courseContext?.title ? 'Continue this course' : 'Continue in order'}

    + +
    + + +
    +
    + ) : null}
    +
    +
    + ) : pageType === 'prompt' ? ( +
    +
    +
    +
    +
    +
    +
    +

    Preview artwork

    + {promptPreviewImage ? Click to zoom : null} +
    + + +
    +
    + +
    +
    +
    + {academyBreadcrumbs.length ? ( +
    + +
    + ) : null} + +
    + Skinbase AI Academy + {lessonCategory} + {lessonDifficulty} + {item.aspect_ratio ? {item.aspect_ratio} : null} + {item.prompt_of_week ? Prompt of the week : null} + {item.featured ? Featured : null} +
    + +

    Prompt template

    +

    {item.title}

    +

    {lessonSummary}

    + +
    + {saveUrl ? : null} + {promptBody ? : null} + {item.negative_prompt ? : null} +
    + +
    + + + + +
    + + {lessonTags.length ? ( +
    +

    Microtags

    +
    + {lessonTags.map((tag) => ( + {tag} + ))} +
    +
    + ) : null} + +
    +
    +

    Prompt status

    +

    + {item.locked + ? 'This page shows the prompt summary, but the full prompt text and editor notes stay locked until your Academy access level matches the template.' + : 'This template includes the main prompt, reuse guidance, and model-specific comparison notes in one place.'} +

    +
    + + {promptModelsCovered.length ? ( +
    +
    +
    +

    Compared with

    +

    {promptModelsCovered.length} model{promptModelsCovered.length > 1 ? 's' : ''} documented for this prompt.

    +
    + {promptModelsCovered.length} +
    +
    + {promptModelsCovered.map((model) => ( + {model} + ))} +
    +
    + ) : null} +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +

    Prompt body

    +

    Prompt text and exclusions

    +
    +
    + +
    +
    +
    +
    +

    {promptHasFullAccess ? 'Full prompt' : 'Preview prompt'}

    +

    {promptHasFullAccess ? 'Ready to paste into your generation workflow.' : 'Upgrade your Academy access to reveal the complete prompt text.'}

    +
    +
    +
    {promptBody || 'Prompt text is not available yet.'}
    +
    + + {item.negative_prompt ? ( +
    +

    Negative prompt

    +
    {item.negative_prompt}
    +
    + ) : null} +
    +
    + + {(promptUsageNotes || promptWorkflowNotes) ? ( +
    +
    +
    +

    Prompt guidance

    +

    How to use this prompt

    +
    + {!promptHasFullAccess ? Full notes visible with access : null} +
    + +
    + {promptUsageNotes ? ( +
    +

    Usage notes

    +

    {promptUsageNotes}

    +
    + ) : null} + + {promptWorkflowNotes ? ( +
    +

    Workflow notes

    +

    {promptWorkflowNotes}

    +
    + ) : null} +
    +
    + ) : null} + + {promptComparisons.length ? ( +
    +
    +

    AI model comparisons

    +

    How different models respond to the same prompt

    +

    Use these notes to decide which provider fits the result you want before you start tuning or post-processing.

    +
    + +
    + {promptComparisons.map((note, index) => )} +
    +
    + ) : null} +
    + +
    ) : (
    - {pageType === 'prompt' ? ( -
    -
    -

    Prompt

    -
    {item.prompt || item.prompt_preview}
    -
    - {item.negative_prompt ?

    Negative prompt

    {item.negative_prompt}
    : null} -
    - ) : null} {pageType === 'pack' ? (

    {item.description}

    @@ -686,6 +1356,8 @@ export default function AcademyShow({ pageType, item, relatedLessons = [], seo,
    )}
    + + setLightboxGallery(null)} onNavigate={navigateLightboxGallery} /> ) } \ No newline at end of file diff --git a/resources/js/Pages/Admin/Academy/CourseBuilder.jsx b/resources/js/Pages/Admin/Academy/CourseBuilder.jsx new file mode 100644 index 00000000..51d3036d --- /dev/null +++ b/resources/js/Pages/Admin/Academy/CourseBuilder.jsx @@ -0,0 +1,540 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { Head, Link, router, useForm, usePage } from '@inertiajs/react' +import AdminLayout from '../../../Layouts/AdminLayout' +import NovaSelect from '../../../components/ui/NovaSelect' + +function laneKey(sectionId) { + return sectionId == null ? 'unsectioned' : `section:${sectionId}` +} + +function sortSections(items = []) { + return [...items].sort((left, right) => { + const orderDiff = Number(left?.order_num || 0) - Number(right?.order_num || 0) + if (orderDiff !== 0) return orderDiff + return Number(left?.id || 0) - Number(right?.id || 0) + }) +} + +function sortLessons(items = []) { + return [...items].sort((left, right) => { + const leftSection = left?.section_id == null ? -1 : Number(left.section_id) + const rightSection = right?.section_id == null ? -1 : Number(right.section_id) + + if (leftSection !== rightSection) return leftSection - rightSection + + const orderDiff = Number(left?.order_num || 0) - Number(right?.order_num || 0) + if (orderDiff !== 0) return orderDiff + + return Number(left?.id || 0) - Number(right?.id || 0) + }) +} + +function buildLessonLanes(sections = [], lessons = []) { + const orderedSections = sortSections(sections) + const orderedLessons = sortLessons(lessons) + + return [ + { + key: 'unsectioned', + sectionId: null, + title: 'Core lessons', + description: 'Lessons shown before the course branches into sections.', + isVisible: true, + lessons: orderedLessons.filter((lesson) => lesson.section_id == null), + }, + ...orderedSections.map((section) => ({ + key: laneKey(section.id), + sectionId: section.id, + title: section.title, + description: section.description || 'Section lessons appear together in this stage.', + isVisible: Boolean(section.is_visible), + lessons: orderedLessons.filter((lesson) => Number(lesson.section_id) === Number(section.id)), + })), + ] +} + +function reindexLessonsFromLanes(sections = [], lessons = []) { + const lanes = buildLessonLanes(sections, lessons) + + return lanes.flatMap((lane) => lane.lessons.map((lesson, index) => ({ + ...lesson, + section_id: lane.sectionId, + order_num: index, + }))) +} + +function moveLessonToPosition(sections = [], lessons = [], lessonId, nextSectionId, targetIndex) { + const lanes = buildLessonLanes(sections, lessons).map((lane) => ({ ...lane, lessons: [...lane.lessons] })) + let draggedLesson = null + + lanes.forEach((lane) => { + const lessonIndex = lane.lessons.findIndex((lesson) => Number(lesson.id) === Number(lessonId)) + if (lessonIndex === -1) return + draggedLesson = { ...lane.lessons[lessonIndex], section_id: nextSectionId } + lane.lessons.splice(lessonIndex, 1) + }) + + if (!draggedLesson) return lessons + + const destinationLane = lanes.find((lane) => lane.sectionId === nextSectionId) + if (!destinationLane) return lessons + + const nextIndex = Math.max(0, Math.min(Number(targetIndex), destinationLane.lessons.length)) + destinationLane.lessons.splice(nextIndex, 0, draggedLesson) + + return reindexLessonsFromLanes(sections, lanes.flatMap((lane) => lane.lessons)) +} + +function shiftLesson(sections = [], lessons = [], lessonId, direction) { + const lanes = buildLessonLanes(sections, lessons) + + for (const lane of lanes) { + const lessonIndex = lane.lessons.findIndex((lesson) => Number(lesson.id) === Number(lessonId)) + if (lessonIndex === -1) continue + + const nextIndex = lessonIndex + direction + if (nextIndex < 0 || nextIndex >= lane.lessons.length) { + return lessons + } + + return moveLessonToPosition(sections, lessons, lessonId, lane.sectionId, nextIndex) + } + + return lessons +} + +function placementSignature(lessons = []) { + return JSON.stringify(sortLessons(lessons).map((lesson) => ({ + id: Number(lesson.id), + section_id: lesson.section_id == null ? null : Number(lesson.section_id), + order_num: Number(lesson.order_num || 0), + }))) +} + +function formatStepLabel(value) { + return `Step ${String(value).padStart(2, '0')}` +} + +function resolveDraggedLessonId(event, fallbackLessonId = null) { + const nativeLessonId = event?.dataTransfer?.getData('text/plain') || '' + + if (nativeLessonId !== '') { + return Number(nativeLessonId) + } + + return fallbackLessonId == null ? null : Number(fallbackLessonId) +} + +function FormCard({ title, description, children }) { + return ( +
    +
    +

    Course builder

    +

    {title}

    + {description ?

    {description}

    : null} +
    + {children} +
    + ) +} + +function CheckboxCardField({ label, checked, onChange, description }) { + return ( + + ) +} + +function EditableSectionCard({ section }) { + const form = useForm({ + title: section.title || '', + slug: section.slug || '', + description: section.description || '', + order_num: section.order_num || 0, + is_visible: Boolean(section.is_visible), + }) + + return ( +
    { event.preventDefault(); form.patch(section.updateUrl) }} className="rounded-[24px] border border-white/10 bg-black/20 p-4"> +
    + + +