Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,36 @@
<?php
namespace Database\Seeders;
use Database\Seeders\NovaCardCategorySeeder;
use Database\Seeders\NovaCardDemoSeeder;
use Database\Seeders\NovaCardTemplateSeeder;
use Database\Seeders\NewsLaunchSeeder;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
$this->call([
NovaCardCategorySeeder::class,
NovaCardTemplateSeeder::class,
NovaCardDemoSeeder::class,
NewsLaunchSeeder::class,
]);
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
}
}

View File

@@ -0,0 +1,173 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use cPad\Plugins\News\Models\NewsArticle;
use cPad\Plugins\News\Models\NewsCategory;
use cPad\Plugins\News\Models\NewsTag;
final class NewsLaunchSeeder extends Seeder
{
public function run(): void
{
$author = User::query()->firstWhere('email', 'newsroom@skinbase.local');
if (! $author) {
$author = new User();
$author->forceFill([
'name' => 'Skinbase Editorial',
'username' => 'skinbaseeditorial',
'email' => 'newsroom@skinbase.local',
'password' => Hash::make('password'),
'role' => 'moderator',
])->save();
$author = User::query()->firstWhere('email', 'newsroom@skinbase.local');
}
if (! $author) {
return;
}
$categories = [
'platform' => $this->upsertCategory('Platform', 'platform', 'Product updates, roadmap notes, and feature launches.'),
'groups' => $this->upsertCategory('Groups', 'groups', 'Stories about collaborative publishing, teams, and shared identity.'),
'tutorials' => $this->upsertCategory('Tutorials', 'tutorials', 'Practical guides for getting more from Nova.'),
'spotlight' => $this->upsertCategory('Spotlight', 'spotlight', 'Featured creators, groups, and standout work.'),
'releases' => $this->upsertCategory('Releases', 'releases', 'Coverage for launches, drops, and project milestones.'),
];
$tags = [
'nova' => $this->upsertTag('Nova', 'nova'),
'tutorial' => $this->upsertTag('Tutorial', 'tutorial'),
'groups' => $this->upsertTag('Groups', 'groups'),
'release' => $this->upsertTag('Release', 'release'),
'spotlight' => $this->upsertTag('Spotlight', 'spotlight'),
'platform-update' => $this->upsertTag('Platform Update', 'platform-update'),
];
$articles = [
[
'slug' => 'welcome-to-skinbase-nova',
'title' => 'Welcome to Skinbase Nova',
'type' => NewsArticle::TYPE_PLATFORM_UPDATE,
'category' => $categories['platform'],
'excerpt' => 'A first look at the refreshed Skinbase experience and the editorial direction behind Nova.',
'content' => "# Welcome to Skinbase Nova\n\nSkinbase Nova brings publishing, discovery, Groups, and editorial storytelling into a single platform experience.\n\n## What is new\n\n- a dedicated newsroom\n- stronger creator identity surfaces\n- deeper internal linking across Groups, releases, and profiles\n- cleaner editorial publishing tools inside Studio\n\nNova is designed to feel active, curated, and connected to the people making the work.",
'tags' => [$tags['nova'], $tags['platform-update']],
'days_ago' => 10,
'featured' => true,
'pinned' => true,
],
[
'slug' => 'introducing-groups-in-nova',
'title' => 'Introducing Groups in Nova',
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'category' => $categories['groups'],
'excerpt' => 'Groups give collaborative identities a real home across publishing, releases, events, and project storytelling.',
'content' => "# Introducing Groups\n\nGroups let creators publish under a shared identity while still keeping contributor context visible.\n\n## Why it matters\n\nGroups are more than labels. They support:\n\n- shared publishing\n- releases and milestones\n- recruiting and discovery\n- public storytelling\n\nThat makes Nova feel much more alive than a simple profile-only platform.",
'tags' => [$tags['groups'], $tags['nova']],
'days_ago' => 9,
'featured' => true,
'pinned' => false,
],
[
'slug' => 'how-studio-works-in-nova',
'title' => 'How Studio Works in Nova',
'type' => NewsArticle::TYPE_TUTORIAL,
'category' => $categories['tutorials'],
'excerpt' => 'A practical walkthrough of drafts, scheduling, content editing, and publishing flow inside Studio.',
'content' => "# How Studio Works\n\nStudio is the control layer for publishing in Nova.\n\n## Start with drafts\n\nUse Studio to prepare content, tune metadata, and move work through review before publishing.\n\n## Move with intent\n\nThe best results come from treating Studio as a workflow, not just a form. Draft, refine, preview, then publish.",
'tags' => [$tags['tutorial'], $tags['nova']],
'days_ago' => 7,
'featured' => false,
'pinned' => false,
],
[
'slug' => 'how-to-upload-your-first-artwork',
'title' => 'How to Upload Your First Artwork',
'type' => NewsArticle::TYPE_TUTORIAL,
'category' => $categories['tutorials'],
'excerpt' => 'A short guide to cleaner uploads, stronger metadata, and better first impressions.',
'content' => "# Upload Your First Artwork\n\nA strong upload starts with the basics:\n\n- a clear title\n- a readable description\n- accurate categories and tags\n- a polished thumbnail or cover\n\nGood publishing habits make discovery, search, and editorial coverage work better for you.",
'tags' => [$tags['tutorial']],
'days_ago' => 6,
'featured' => false,
'pinned' => false,
],
[
'slug' => 'creator-spotlight-building-a-recognizable-profile',
'title' => 'Creator Spotlight: Building a Recognizable Profile',
'type' => NewsArticle::TYPE_SPOTLIGHT,
'category' => $categories['spotlight'],
'excerpt' => 'Profiles work better when identity, consistency, and a clear body of work all point in the same direction.',
'content' => "# Creator Spotlight\n\nGreat profiles are memorable because they feel intentional.\n\n## Strong profile signals\n\n- consistent visual identity\n- complete bio and links\n- a curated set of standout uploads\n- a clear publishing rhythm\n\nNova rewards clarity and consistency.",
'tags' => [$tags['spotlight']],
'days_ago' => 4,
'featured' => false,
'pinned' => false,
],
[
'slug' => 'release-roundup-whats-new-this-week',
'title' => 'Release Roundup: What\'s New This Week',
'type' => NewsArticle::TYPE_RELEASE,
'category' => $categories['releases'],
'excerpt' => 'A compact editorial roundup of recent launches, notable drops, and community momentum.',
'content' => "# Release Roundup\n\nThis week\'s standout launches show why Nova needs editorial context alongside uploads.\n\nRelease coverage helps people discover:\n\n- the work itself\n- the team behind it\n- related projects and collections\n- where the story continues next",
'tags' => [$tags['release'], $tags['spotlight']],
'days_ago' => 2,
'featured' => false,
'pinned' => false,
],
];
foreach ($articles as $article) {
$record = NewsArticle::query()->firstOrNew(['slug' => $article['slug']]);
$record->forceFill([
'title' => $article['title'],
'slug' => $article['slug'],
'excerpt' => $article['excerpt'],
'content' => $article['content'],
'author_id' => $author->id,
'category_id' => $article['category']->id,
'type' => $article['type'],
'status' => 'published',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_PUBLISHED,
'published_at' => Carbon::now()->subDays($article['days_ago'])->setTime(10, 0),
'is_featured' => $article['featured'],
'is_pinned' => $article['pinned'],
'meta_title' => $article['title'],
'meta_description' => $article['excerpt'],
'deleted_at' => null,
])->save();
$record->tags()->sync(array_map(static fn (NewsTag $tag): int => (int) $tag->id, $article['tags']));
}
}
private function upsertCategory(string $name, string $slug, string $description): NewsCategory
{
return NewsCategory::query()->updateOrCreate(
['slug' => $slug],
[
'name' => $name,
'description' => $description,
'position' => 0,
'is_active' => true,
]
);
}
private function upsertTag(string $name, string $slug): NewsTag
{
return NewsTag::query()->updateOrCreate(
['slug' => $slug],
['name' => $name]
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\NovaCardCategory;
use Illuminate\Database\Seeder;
class NovaCardCategorySeeder extends Seeder
{
public function run(): void
{
$categories = [
['slug' => 'motivation', 'name' => 'Motivation'],
['slug' => 'love', 'name' => 'Love'],
['slug' => 'life', 'name' => 'Life'],
['slug' => 'happiness', 'name' => 'Happiness'],
['slug' => 'healing', 'name' => 'Healing'],
['slug' => 'friendship', 'name' => 'Friendship'],
['slug' => 'aesthetic', 'name' => 'Aesthetic'],
['slug' => 'minimal', 'name' => 'Minimal'],
['slug' => 'dark-mood', 'name' => 'Dark Mood'],
['slug' => 'poetry', 'name' => 'Poetry'],
['slug' => 'wallpaper-quotes', 'name' => 'Wallpaper Quotes'],
];
foreach ($categories as $index => $category) {
NovaCardCategory::query()->updateOrCreate(
['slug' => $category['slug']],
[
'name' => $category['name'],
'description' => sprintf('%s cards and shareable quote visuals.', $category['name']),
'active' => true,
'order_num' => $index,
]
);
}
}
}

View File

@@ -0,0 +1,357 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\NovaCard;
use App\Models\NovaCardAsset;
use App\Models\NovaCardAssetPack;
use App\Models\NovaCardCategory;
use App\Models\NovaCardChallenge;
use App\Models\NovaCardChallengeEntry;
use App\Models\NovaCardCollection;
use App\Models\NovaCardCollectionItem;
use App\Models\NovaCardTag;
use App\Models\NovaCardTemplate;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class NovaCardDemoSeeder extends Seeder
{
public function run(): void
{
if (! (bool) config('nova_cards.seed_demo_cards.enabled', false)) {
return;
}
$userConfig = (array) config('nova_cards.seed_demo_cards.user', []);
$user = User::query()->firstOrCreate(
['email' => (string) Arr::get($userConfig, 'email', 'nova-cards-demo@skinbase.test')],
[
'username' => (string) Arr::get($userConfig, 'username', 'nova.cards'),
'name' => (string) Arr::get($userConfig, 'name', 'Nova Cards'),
'password' => (string) Arr::get($userConfig, 'password', 'password'),
'role' => 'user',
]
);
$cards = [
[
'slug' => 'official-spark',
'title' => 'Official Spark',
'quote_text' => 'Small moments of focus turn into visible momentum.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Launch Collection',
'description' => 'An official Nova Cards demo card for featured browse surfaces.',
'category_slug' => 'motivation',
'template_slug' => 'neon-nova',
'format' => NovaCard::FORMAT_SQUARE,
'featured' => true,
'tags' => ['focus', 'launch'],
'project_json' => [
'layout' => ['layout' => 'quote_heavy', 'position' => 'center', 'alignment' => 'center', 'padding' => 'comfortable', 'max_width' => 'balanced'],
'typography' => ['font_preset' => 'bold-poster', 'text_color' => '#e0f2fe', 'accent_color' => '#ffffff', 'quote_size' => 80, 'author_size' => 24, 'letter_spacing' => 1, 'line_height' => 1.05, 'shadow_preset' => 'strong'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'midnight-nova', 'gradient_colors' => ['#0f172a', '#1d4ed8'], 'solid_color' => '#111827', 'overlay_style' => 'dark-strong', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 65],
],
],
[
'slug' => 'soft-breath',
'title' => 'Soft Breath',
'quote_text' => 'Rest is not a pause from growth. It is part of it.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Healing Notes',
'description' => 'A calm demo card showing the softer side of Nova Cards.',
'category_slug' => 'healing',
'template_slug' => 'soft-pastel',
'format' => NovaCard::FORMAT_PORTRAIT,
'featured' => false,
'tags' => ['healing', 'calm'],
'project_json' => [
'layout' => ['layout' => 'centered', 'position' => 'center', 'alignment' => 'center', 'padding' => 'airy', 'max_width' => 'compact'],
'typography' => ['font_preset' => 'soft-handwritten', 'text_color' => '#1f2937', 'accent_color' => '#6d28d9', 'quote_size' => 72, 'author_size' => 24, 'letter_spacing' => 0, 'line_height' => 1.2, 'shadow_preset' => 'none'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'soft-pastel', 'gradient_colors' => ['#f9a8d4', '#c4b5fd'], 'solid_color' => '#f5d0fe', 'overlay_style' => 'light-soft', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 35],
],
],
[
'slug' => 'night-echo',
'title' => 'Night Echo',
'quote_text' => 'Not every quiet room is empty. Some are full of answers.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Dark Mood Study',
'description' => 'A darker official demo card for mood-oriented discovery blocks.',
'category_slug' => 'dark-mood',
'template_slug' => 'cinematic-dark',
'format' => NovaCard::FORMAT_LANDSCAPE,
'featured' => false,
'tags' => ['night', 'mood'],
'project_json' => [
'layout' => ['layout' => 'minimal', 'position' => 'lower-middle', 'alignment' => 'left', 'padding' => 'comfortable', 'max_width' => 'wide'],
'typography' => ['font_preset' => 'modern-sans', 'text_color' => '#f8fafc', 'accent_color' => '#93c5fd', 'quote_size' => 68, 'author_size' => 20, 'letter_spacing' => 1, 'line_height' => 1.2, 'shadow_preset' => 'soft'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'deep-cinema', 'gradient_colors' => ['#020617', '#334155'], 'solid_color' => '#020617', 'overlay_style' => 'dark-strong', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 80],
],
],
[
'slug' => 'editorial-glow',
'title' => 'Editorial Glow',
'quote_text' => 'Design with restraint, then let one accent do the speaking.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Editorial Kit',
'description' => 'A crisp editorial-format demo card for official collections.',
'category_slug' => 'motivation',
'template_slug' => 'golden-serif',
'format' => NovaCard::FORMAT_PORTRAIT,
'featured' => true,
'tags' => ['editorial', 'gold'],
'project_json' => [
'layout' => ['layout' => 'author_emphasis', 'position' => 'center', 'alignment' => 'center', 'padding' => 'comfortable', 'max_width' => 'compact'],
'typography' => ['font_preset' => 'elegant-serif', 'text_color' => '#fffbeb', 'accent_color' => '#fbbf24', 'quote_size' => 76, 'author_size' => 24, 'letter_spacing' => 1, 'line_height' => 1.15, 'shadow_preset' => 'soft'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'amber-glow', 'gradient_colors' => ['#451a03', '#b45309'], 'solid_color' => '#451a03', 'overlay_style' => 'dark-soft', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 70],
],
],
[
'slug' => 'story-bloom',
'title' => 'Story Bloom',
'quote_text' => 'If the layout breathes, the words can reach further.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Story Vertical Pack',
'description' => 'A vertical story-oriented demo card for public browsing and challenges.',
'category_slug' => 'healing',
'template_slug' => 'story-vertical',
'format' => NovaCard::FORMAT_STORY,
'featured' => false,
'tags' => ['story', 'vertical'],
'project_json' => [
'layout' => ['layout' => 'centered', 'position' => 'center', 'alignment' => 'center', 'padding' => 'airy', 'max_width' => 'balanced'],
'typography' => ['font_preset' => 'modern-sans', 'text_color' => '#ecfdf5', 'accent_color' => '#6ee7b7', 'quote_size' => 82, 'author_size' => 24, 'letter_spacing' => 0, 'line_height' => 1.18, 'shadow_preset' => 'soft'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'emerald-bloom', 'gradient_colors' => ['#064e3b', '#10b981'], 'solid_color' => '#022c22', 'overlay_style' => 'dark-soft', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 65],
],
],
[
'slug' => 'remix-launch-variant',
'title' => 'Remix Launch Variant',
'quote_text' => 'Take the spark and give it a new rhythm.',
'quote_author' => 'Skinbase Nova',
'quote_source' => 'Remix Lab',
'description' => 'A seeded remix showing lineage in demo content.',
'category_slug' => 'motivation',
'template_slug' => 'bold-statement',
'format' => NovaCard::FORMAT_SQUARE,
'featured' => false,
'tags' => ['remix', 'launch'],
'project_json' => [
'layout' => ['layout' => 'quote_heavy', 'position' => 'center', 'alignment' => 'left', 'padding' => 'comfortable', 'max_width' => 'wide'],
'typography' => ['font_preset' => 'bold-poster', 'text_color' => '#ffffff', 'accent_color' => '#38bdf8', 'quote_size' => 80, 'author_size' => 22, 'letter_spacing' => 1, 'line_height' => 1.05, 'shadow_preset' => 'strong'],
'background' => ['type' => 'gradient', 'gradient_preset' => 'midnight-nova', 'gradient_colors' => ['#082f49', '#1d4ed8'], 'solid_color' => '#0f172a', 'overlay_style' => 'dark-strong', 'focal_position' => 'center', 'blur_level' => 0, 'opacity' => 75],
],
],
];
$seededCards = collect();
foreach ($cards as $index => $definition) {
$category = NovaCardCategory::query()->where('slug', $definition['category_slug'])->first();
$template = NovaCardTemplate::query()->where('slug', $definition['template_slug'])->first();
$card = NovaCard::query()->updateOrCreate(
['slug' => $definition['slug']],
[
'uuid' => NovaCard::query()->where('slug', $definition['slug'])->value('uuid') ?: (string) Str::uuid(),
'user_id' => $user->id,
'category_id' => $category?->id,
'template_id' => $template?->id,
'title' => $definition['title'],
'quote_text' => $definition['quote_text'],
'quote_author' => $definition['quote_author'],
'quote_source' => $definition['quote_source'],
'description' => $definition['description'],
'format' => $definition['format'],
'project_json' => [
'content' => [
'title' => $definition['title'],
'quote_text' => $definition['quote_text'],
'quote_author' => $definition['quote_author'],
'quote_source' => $definition['quote_source'],
],
...$definition['project_json'],
'decorations' => [],
],
'render_version' => 1,
'background_type' => 'gradient',
'visibility' => NovaCard::VISIBILITY_PUBLIC,
'status' => NovaCard::STATUS_PUBLISHED,
'moderation_status' => NovaCard::MOD_APPROVED,
'featured' => (bool) $definition['featured'],
'allow_download' => true,
'views_count' => 25 - ($index * 4),
'shares_count' => 6 - $index,
'downloads_count' => 3 - min($index, 2),
'likes_count' => max(1, 8 - $index),
'favorites_count' => max(0, 5 - $index),
'saves_count' => max(0, 4 - $index),
'published_at' => now()->subDays($index + 1),
]
);
$tagIds = collect($definition['tags'])
->map(function (string $tag): int {
$model = NovaCardTag::query()->firstOrCreate(
['slug' => Str::slug($tag)],
['name' => Str::title(str_replace('-', ' ', $tag))]
);
return (int) $model->id;
})
->all();
$card->tags()->sync($tagIds);
$seededCards->push($card->fresh());
}
$source = $seededCards->firstWhere('slug', 'official-spark');
$remix = $seededCards->firstWhere('slug', 'remix-launch-variant');
if ($source && $remix) {
$remix->forceFill([
'original_card_id' => $source->id,
'root_card_id' => $source->id,
'remixes_count' => 0,
])->save();
$source->forceFill([
'remixes_count' => 1,
])->save();
}
foreach (array_merge(
(array) config('nova_cards.asset_packs', []),
(array) config('nova_cards.template_packs', [])
) as $index => $packConfig) {
$pack = NovaCardAssetPack::query()->updateOrCreate(
['slug' => (string) ($packConfig['slug'] ?? ('pack-' . $index))],
[
'name' => (string) ($packConfig['name'] ?? 'Nova Card Pack'),
'description' => $packConfig['description'] ?? null,
'type' => (string) ($packConfig['type'] ?? NovaCardAssetPack::TYPE_ASSET),
'manifest_json' => $packConfig['manifest_json'] ?? [],
'official' => (bool) ($packConfig['official'] ?? true),
'active' => (bool) ($packConfig['active'] ?? true),
'order_num' => $index,
]
);
foreach ((array) data_get($packConfig, 'manifest_json.items', []) as $itemIndex => $item) {
NovaCardAsset::query()->updateOrCreate(
['asset_pack_id' => $pack->id, 'asset_key' => (string) ($item['key'] ?? ('asset-' . $itemIndex))],
[
'label' => (string) ($item['label'] ?? 'Nova Asset'),
'type' => (string) ($item['type'] ?? 'glyph'),
'preview_image' => $item['preview_image'] ?? null,
'data_json' => $item,
'official' => true,
'active' => true,
'order_num' => $itemIndex,
]
);
}
}
$collection = NovaCardCollection::query()->updateOrCreate(
['user_id' => $user->id, 'slug' => 'editorial-favorites'],
[
'name' => 'Editorial Favorites',
'description' => 'Officially curated Nova Cards spotlighting launch visuals, remixes, and story-first layouts.',
'visibility' => NovaCardCollection::VISIBILITY_PUBLIC,
'official' => true,
'featured' => true,
'cards_count' => 0,
]
);
$challengeCollection = NovaCardCollection::query()->updateOrCreate(
['user_id' => $user->id, 'slug' => 'challenge-winners'],
[
'name' => 'Challenge Winners',
'description' => 'Official challenge highlights and standout seeded examples.',
'visibility' => NovaCardCollection::VISIBILITY_PUBLIC,
'official' => true,
'featured' => false,
'cards_count' => 0,
]
);
foreach ($seededCards->take(4)->values() as $position => $card) {
NovaCardCollectionItem::query()->updateOrCreate(
['collection_id' => $collection->id, 'card_id' => $card->id],
['sort_order' => $position + 1, 'note' => $position === 0 ? 'Launch anchor card for the collection.' : null],
);
}
$collection->forceFill(['cards_count' => NovaCardCollectionItem::query()->where('collection_id', $collection->id)->count()])->save();
foreach ($seededCards->slice(2, 4)->values() as $position => $card) {
NovaCardCollectionItem::query()->updateOrCreate(
['collection_id' => $challengeCollection->id, 'card_id' => $card->id],
['sort_order' => $position + 1, 'note' => $position === 0 ? 'Seeded challenge highlight.' : null],
);
}
$challengeCollection->forceFill(['cards_count' => NovaCardCollectionItem::query()->where('collection_id', $challengeCollection->id)->count()])->save();
$challenge = NovaCardChallenge::query()->updateOrCreate(
['slug' => 'launch-remix-run'],
[
'user_id' => $user->id,
'title' => 'Launch Remix Run',
'description' => 'A seeded official challenge for remix-ready launch cards.',
'prompt' => 'Remix one official card and push the layout into a fresh direction.',
'status' => NovaCardChallenge::STATUS_ACTIVE,
'official' => true,
'featured' => true,
'starts_at' => now()->subDays(2),
'ends_at' => now()->addDays(10),
]
);
$secondaryChallenge = NovaCardChallenge::query()->updateOrCreate(
['slug' => 'minimal-poster-week'],
[
'user_id' => $user->id,
'title' => 'Minimal Poster Week',
'description' => 'A seeded editorial challenge focused on restraint and poster typography.',
'prompt' => 'Build a bold quote poster with minimal decoration and strong hierarchy.',
'status' => NovaCardChallenge::STATUS_COMPLETED,
'official' => true,
'featured' => false,
'starts_at' => now()->subDays(14),
'ends_at' => now()->subDays(3),
]
);
foreach ($seededCards->take(3)->values() as $position => $card) {
NovaCardChallengeEntry::query()->updateOrCreate(
['challenge_id' => $challenge->id, 'card_id' => $card->id],
['user_id' => $user->id, 'status' => $position === 0 ? 'featured' : 'active', 'note' => $position === 0 ? 'Seeded featured entry.' : null],
);
}
foreach ($seededCards->slice(3, 3)->values() as $position => $card) {
NovaCardChallengeEntry::query()->updateOrCreate(
['challenge_id' => $secondaryChallenge->id, 'card_id' => $card->id],
['user_id' => $user->id, 'status' => $position === 0 ? 'winner' : 'active', 'note' => $position === 0 ? 'Seeded winner entry.' : null],
);
}
$challenge->forceFill([
'entries_count' => NovaCardChallengeEntry::query()->where('challenge_id', $challenge->id)->count(),
'winner_card_id' => $seededCards->first()?->id,
])->save();
$secondaryChallenge->forceFill([
'entries_count' => NovaCardChallengeEntry::query()->where('challenge_id', $secondaryChallenge->id)->count(),
'winner_card_id' => $seededCards->get(3)?->id,
])->save();
}
}

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\NovaCardTemplate;
use Illuminate\Database\Seeder;
class NovaCardTemplateSeeder extends Seeder
{
public function run(): void
{
$templates = [
[
'slug' => 'minimal-black-white',
'name' => 'Minimal Black / White',
'description' => 'High-contrast minimalist typography.',
'formats' => ['square', 'portrait', 'landscape'],
'config' => ['font_preset' => 'minimal-editorial', 'gradient_preset' => 'deep-cinema', 'text_align' => 'left', 'layout' => 'minimal', 'text_color' => '#ffffff', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'neon-nova',
'name' => 'Neon Nova',
'description' => 'Bright accents with deep night gradients.',
'formats' => ['square', 'portrait', 'story'],
'config' => ['font_preset' => 'bold-poster', 'gradient_preset' => 'midnight-nova', 'text_align' => 'center', 'layout' => 'centered', 'text_color' => '#e0f2fe', 'overlay_style' => 'dark-strong'],
],
[
'slug' => 'soft-pastel',
'name' => 'Soft Pastel',
'description' => 'Soft pastel tones for uplifting cards.',
'formats' => ['square', 'portrait'],
'config' => ['font_preset' => 'soft-handwritten', 'gradient_preset' => 'soft-pastel', 'text_align' => 'center', 'layout' => 'quote_heavy', 'text_color' => '#1f2937', 'overlay_style' => 'light-soft'],
],
[
'slug' => 'romantic',
'name' => 'Romantic',
'description' => 'Warm romantic quote treatments.',
'formats' => ['square', 'portrait', 'story'],
'config' => ['font_preset' => 'elegant-serif', 'gradient_preset' => 'romantic-dusk', 'text_align' => 'center', 'layout' => 'author_emphasis', 'text_color' => '#fff1f2', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'cinematic-dark',
'name' => 'Cinematic Dark',
'description' => 'Moody cinematic composition.',
'formats' => ['square', 'portrait', 'landscape'],
'config' => ['font_preset' => 'modern-sans', 'gradient_preset' => 'deep-cinema', 'text_align' => 'left', 'layout' => 'quote_heavy', 'text_color' => '#f8fafc', 'overlay_style' => 'dark-strong'],
],
[
'slug' => 'golden-serif',
'name' => 'Golden Serif',
'description' => 'Elegant serif layout with warm highlights.',
'formats' => ['square', 'portrait', 'landscape'],
'config' => ['font_preset' => 'elegant-serif', 'gradient_preset' => 'amber-glow', 'text_align' => 'center', 'layout' => 'author_emphasis', 'text_color' => '#fffbeb', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'nature-calm',
'name' => 'Nature Calm',
'description' => 'Fresh greens and sky tones.',
'formats' => ['square', 'portrait', 'story'],
'config' => ['font_preset' => 'dreamy-aesthetic', 'gradient_preset' => 'nature-calm', 'text_align' => 'left', 'layout' => 'centered', 'text_color' => '#ecfeff', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'bold-statement',
'name' => 'Bold Statement',
'description' => 'Large statement typography.',
'formats' => ['square', 'portrait', 'landscape'],
'config' => ['font_preset' => 'bold-poster', 'gradient_preset' => 'midnight-nova', 'text_align' => 'left', 'layout' => 'quote_heavy', 'text_color' => '#ffffff', 'overlay_style' => 'dark-strong'],
],
[
'slug' => 'wallpaper-style',
'name' => 'Wallpaper Style',
'description' => 'Spacious wallpaper-like composition.',
'formats' => ['portrait', 'story', 'landscape'],
'config' => ['font_preset' => 'dreamy-aesthetic', 'gradient_preset' => 'dream-glow', 'text_align' => 'center', 'layout' => 'minimal', 'text_color' => '#fdf4ff', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'story-vertical',
'name' => 'Story Vertical',
'description' => 'Tall mobile-first story layout.',
'formats' => ['story'],
'config' => ['font_preset' => 'modern-sans', 'gradient_preset' => 'emerald-bloom', 'text_align' => 'center', 'layout' => 'centered', 'text_color' => '#ecfdf5', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'dream-glow',
'name' => 'Dream Glow',
'description' => 'Glowing dreamy aesthetic template.',
'formats' => ['square', 'portrait', 'story'],
'config' => ['font_preset' => 'dreamy-aesthetic', 'gradient_preset' => 'dream-glow', 'text_align' => 'center', 'layout' => 'quote_heavy', 'text_color' => '#faf5ff', 'overlay_style' => 'dark-soft'],
],
[
'slug' => 'classic-typography',
'name' => 'Classic Typography',
'description' => 'Classic bookish quote treatment.',
'formats' => ['square', 'portrait', 'landscape'],
'config' => ['font_preset' => 'elegant-serif', 'gradient_preset' => 'amber-glow', 'text_align' => 'left', 'layout' => 'author_emphasis', 'text_color' => '#fff7ed', 'overlay_style' => 'dark-soft'],
],
];
foreach ($templates as $index => $template) {
NovaCardTemplate::query()->updateOrCreate(
['slug' => $template['slug']],
[
'name' => $template['name'],
'description' => $template['description'],
'preview_image' => null,
'config_json' => $template['config'],
'supported_formats' => $template['formats'],
'active' => true,
'official' => true,
'order_num' => $index,
]
);
}
}
}