This commit is contained in:
ct
2025-07-15 00:43:37 +08:00
parent e7c6c02785
commit 8bb6705031
9 changed files with 481 additions and 58 deletions

View File

@@ -24033,6 +24033,317 @@ public static function getContainer()
} }
} }
namespace Maatwebsite\Excel\Facades {
/**
*
*
*/
class Excel {
/**
*
*
* @param object $export
* @param string|null $fileName
* @param string $writerType
* @param array $headers
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @static
*/
public static function download($export, $fileName, $writerType = null, $headers = [])
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->download($export, $fileName, $writerType, $headers);
}
/**
*
*
* @param string|null $disk Fallback for usage with named properties
* @param object $export
* @param string $filePath
* @param string|null $diskName
* @param string $writerType
* @param mixed $diskOptions
* @return bool
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @static
*/
public static function store($export, $filePath, $diskName = null, $writerType = null, $diskOptions = [], $disk = null)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->store($export, $filePath, $diskName, $writerType, $diskOptions, $disk);
}
/**
*
*
* @param object $export
* @param string $filePath
* @param string|null $disk
* @param string $writerType
* @param mixed $diskOptions
* @return \Illuminate\Foundation\Bus\PendingDispatch
* @static
*/
public static function queue($export, $filePath, $disk = null, $writerType = null, $diskOptions = [])
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->queue($export, $filePath, $disk, $writerType, $diskOptions);
}
/**
*
*
* @param object $export
* @param string $writerType
* @return string
* @static
*/
public static function raw($export, $writerType)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->raw($export, $writerType);
}
/**
*
*
* @param object $import
* @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $filePath
* @param string|null $disk
* @param string|null $readerType
* @return \Maatwebsite\Excel\Reader|\Illuminate\Foundation\Bus\PendingDispatch
* @static
*/
public static function import($import, $filePath, $disk = null, $readerType = null)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->import($import, $filePath, $disk, $readerType);
}
/**
*
*
* @param object $import
* @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $filePath
* @param string|null $disk
* @param string|null $readerType
* @return array
* @static
*/
public static function toArray($import, $filePath, $disk = null, $readerType = null)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->toArray($import, $filePath, $disk, $readerType);
}
/**
*
*
* @param object $import
* @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $filePath
* @param string|null $disk
* @param string|null $readerType
* @return \Illuminate\Support\Collection
* @static
*/
public static function toCollection($import, $filePath, $disk = null, $readerType = null)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->toCollection($import, $filePath, $disk, $readerType);
}
/**
*
*
* @param \Illuminate\Contracts\Queue\ShouldQueue $import
* @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $filePath
* @param string|null $disk
* @param string $readerType
* @return \Illuminate\Foundation\Bus\PendingDispatch
* @static
*/
public static function queueImport($import, $filePath, $disk = null, $readerType = null)
{
/** @var \Maatwebsite\Excel\Excel $instance */
return $instance->queueImport($import, $filePath, $disk, $readerType);
}
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
* @param-closure-this static $macro
* @return void
* @static
*/
public static function macro($name, $macro)
{
\Maatwebsite\Excel\Excel::macro($name, $macro);
}
/**
* Mix another object into the class.
*
* @param object $mixin
* @param bool $replace
* @return void
* @throws \ReflectionException
* @static
*/
public static function mixin($mixin, $replace = true)
{
\Maatwebsite\Excel\Excel::mixin($mixin, $replace);
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
* @static
*/
public static function hasMacro($name)
{
return \Maatwebsite\Excel\Excel::hasMacro($name);
}
/**
* Flush the existing macros.
*
* @return void
* @static
*/
public static function flushMacros()
{
\Maatwebsite\Excel\Excel::flushMacros();
}
/**
*
*
* @param string $concern
* @param callable $handler
* @param string $event
* @static
*/
public static function extend($concern, $handler, $event = 'Maatwebsite\\Excel\\Events\\BeforeWriting')
{
return \Maatwebsite\Excel\Excel::extend($concern, $handler, $event);
}
/**
* When asserting downloaded, stored, queued or imported, use regular expression
* to look for a matching file path.
*
* @return void
* @static
*/
public static function matchByRegex()
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
$instance->matchByRegex();
}
/**
* When asserting downloaded, stored, queued or imported, use regular string
* comparison for matching file path.
*
* @return void
* @static
*/
public static function doNotMatchByRegex()
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
$instance->doNotMatchByRegex();
}
/**
*
*
* @param string $fileName
* @param callable|null $callback
* @static
*/
public static function assertDownloaded($fileName, $callback = null)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertDownloaded($fileName, $callback);
}
/**
*
*
* @param string $filePath
* @param string|callable|null $disk
* @param callable|null $callback
* @static
*/
public static function assertStored($filePath, $disk = null, $callback = null)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertStored($filePath, $disk, $callback);
}
/**
*
*
* @param string $filePath
* @param string|callable|null $disk
* @param callable|null $callback
* @static
*/
public static function assertQueued($filePath, $disk = null, $callback = null)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertQueued($filePath, $disk, $callback);
}
/**
*
*
* @static
*/
public static function assertQueuedWithChain($chain)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertQueuedWithChain($chain);
}
/**
*
*
* @param string $classname
* @param callable|null $callback
* @static
*/
public static function assertExportedInRaw($classname, $callback = null)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertExportedInRaw($classname, $callback);
}
/**
*
*
* @param string $filePath
* @param string|callable|null $disk
* @param callable|null $callback
* @static
*/
public static function assertImported($filePath, $disk = null, $callback = null)
{
/** @var \Maatwebsite\Excel\Fakes\ExcelFake $instance */
return $instance->assertImported($filePath, $disk, $callback);
}
}
}
namespace ProtoneMedia\LaravelFFMpeg\Support { namespace ProtoneMedia\LaravelFFMpeg\Support {
/** /**
* *
@@ -24332,6 +24643,49 @@ public static function getConfig()
} }
} }
namespace Illuminate\Support {
/**
*
*
* @template TKey of array-key
* @template-covariant TValue
* @implements \ArrayAccess<TKey, TValue>
* @implements \Illuminate\Support\Enumerable<TKey, TValue>
*/
class Collection {
/**
*
*
* @see \Maatwebsite\Excel\Mixins\DownloadCollectionMixin::downloadExcel()
* @param string $fileName
* @param string|null $writerType
* @param mixed $withHeadings
* @param array $responseHeaders
* @static
*/
public static function downloadExcel($fileName, $writerType = null, $withHeadings = false, $responseHeaders = [])
{
return \Illuminate\Support\Collection::downloadExcel($fileName, $writerType, $withHeadings, $responseHeaders);
}
/**
*
*
* @see \Maatwebsite\Excel\Mixins\StoreCollectionMixin::storeExcel()
* @param string $filePath
* @param string|null $disk
* @param string|null $writerType
* @param mixed $withHeadings
* @static
*/
public static function storeExcel($filePath, $disk = null, $writerType = null, $withHeadings = false)
{
return \Illuminate\Support\Collection::storeExcel($filePath, $disk, $writerType, $withHeadings);
}
}
}
namespace Illuminate\Http { namespace Illuminate\Http {
/** /**
* *
@@ -26881,6 +27235,64 @@ public static function mergeConstraintsFrom($from)
return $instance->mergeConstraintsFrom($from); return $instance->mergeConstraintsFrom($from);
} }
/**
*
*
* @see \Maatwebsite\Excel\Mixins\DownloadQueryMacro::__invoke()
* @param string $fileName
* @param string|null $writerType
* @param mixed $withHeadings
* @static
*/
public static function downloadExcel($fileName, $writerType = null, $withHeadings = false)
{
return \Illuminate\Database\Eloquent\Builder::downloadExcel($fileName, $writerType, $withHeadings);
}
/**
*
*
* @see \Maatwebsite\Excel\Mixins\StoreQueryMacro::__invoke()
* @param string $filePath
* @param string|null $disk
* @param string|null $writerType
* @param mixed $withHeadings
* @static
*/
public static function storeExcel($filePath, $disk = null, $writerType = null, $withHeadings = false)
{
return \Illuminate\Database\Eloquent\Builder::storeExcel($filePath, $disk, $writerType, $withHeadings);
}
/**
*
*
* @see \Maatwebsite\Excel\Mixins\ImportMacro::__invoke()
* @param string $filename
* @param string|null $disk
* @param string|null $readerType
* @static
*/
public static function import($filename, $disk = null, $readerType = null)
{
return \Illuminate\Database\Eloquent\Builder::import($filename, $disk, $readerType);
}
/**
*
*
* @see \Maatwebsite\Excel\Mixins\ImportAsMacro::__invoke()
* @param string $filename
* @param callable $mapping
* @param string|null $disk
* @param string|null $readerType
* @static
*/
public static function importAs($filename, $mapping, $disk = null, $readerType = null)
{
return \Illuminate\Database\Eloquent\Builder::importAs($filename, $mapping, $disk, $readerType);
}
/** /**
* Set the columns to be selected. * Set the columns to be selected.
* *
@@ -29666,6 +30078,7 @@ class Twitter extends \Artesaos\SEOTools\Facades\TwitterCard {}
class OpenGraph extends \Artesaos\SEOTools\Facades\OpenGraph {} class OpenGraph extends \Artesaos\SEOTools\Facades\OpenGraph {}
class Horizon extends \Laravel\Horizon\Horizon {} class Horizon extends \Laravel\Horizon\Horizon {}
class Socialite extends \Laravel\Socialite\Facades\Socialite {} class Socialite extends \Laravel\Socialite\Facades\Socialite {}
class Excel extends \Maatwebsite\Excel\Facades\Excel {}
class FFMpeg extends \ProtoneMedia\LaravelFFMpeg\Support\FFMpeg {} class FFMpeg extends \ProtoneMedia\LaravelFFMpeg\Support\FFMpeg {}
class ResponseCache extends \Spatie\ResponseCache\Facades\ResponseCache {} class ResponseCache extends \Spatie\ResponseCache\Facades\ResponseCache {}
class Hashids extends \Vinkla\Hashids\Facades\Hashids {} class Hashids extends \Vinkla\Hashids\Facades\Hashids {}

BIN
database/.DS_Store vendored

Binary file not shown.

View File

@@ -2,8 +2,8 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {

Binary file not shown.

View File

@@ -53,7 +53,7 @@ public function run(): void
$csv_path = database_path('seeders/data/g2.csv'); $csv_path = database_path('seeders/data/g2.csv');
$meme_data = $this->parseCsvFile($csv_path); $meme_data = $this->parseCsvFile($csv_path);
$this->command->info('📊 Found ' . count($meme_data) . ' memes to import'); $this->command->info('📊 Found '.count($meme_data).' memes to import');
// Process records individually for PostgreSQL compatibility // Process records individually for PostgreSQL compatibility
$total_processed = 0; $total_processed = 0;
@@ -61,12 +61,13 @@ public function run(): void
$total_failed = 0; $total_failed = 0;
foreach ($meme_data as $index => $meme_record) { foreach ($meme_data as $index => $meme_record) {
$this->command->info('Processing ' . ($index + 1) . '/' . count($meme_data) . ': ' . $meme_record['filename']); $this->command->info('Processing '.($index + 1).'/'.count($meme_data).': '.$meme_record['filename']);
// Skip empty or malformed records // Skip empty or malformed records
if (empty($meme_record['filename']) || empty($meme_record['type']) || empty($meme_record['name'])) { if (empty($meme_record['filename']) || empty($meme_record['type']) || empty($meme_record['name'])) {
$this->command->warn("⏭️ Skipping malformed CSV record at line " . ($index + 1) . ": missing filename, type, or name"); $this->command->warn('⏭️ Skipping malformed CSV record at line '.($index + 1).': missing filename, type, or name');
$total_skipped++; $total_skipped++;
continue; continue;
} }
@@ -93,12 +94,12 @@ public function run(): void
} }
} else { } else {
$this->command->error("❌ Failed to import: {$meme_record['filename']} - Import returned false"); $this->command->error("❌ Failed to import: {$meme_record['filename']} - Import returned false");
$this->command->error("🛑 Halting seeder to investigate the issue"); $this->command->error('🛑 Halting seeder to investigate the issue');
throw new \RuntimeException("Import failed for {$meme_record['filename']}"); throw new \RuntimeException("Import failed for {$meme_record['filename']}");
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->command->error("❌ Failed to import: {$meme_record['filename']} - {$e->getMessage()}"); $this->command->error("❌ Failed to import: {$meme_record['filename']} - {$e->getMessage()}");
$this->command->error("🛑 Halting seeder to investigate the issue"); $this->command->error('🛑 Halting seeder to investigate the issue');
throw $e; throw $e;
} }
} }
@@ -144,24 +145,24 @@ private function parseCsvFile(string $csv_path): array
// Use Laravel Excel to parse CSV with proper handling of multi-line fields // Use Laravel Excel to parse CSV with proper handling of multi-line fields
$collection = Excel::toCollection(null, $csv_path)->first(); $collection = Excel::toCollection(null, $csv_path)->first();
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new \RuntimeException("CSV file is empty or could not be parsed"); throw new \RuntimeException('CSV file is empty or could not be parsed');
} }
// Get headers from first row // Get headers from first row
$headers = $collection->first()->toArray(); $headers = $collection->first()->toArray();
// Convert remaining rows to associative arrays // Convert remaining rows to associative arrays
$meme_data = []; $meme_data = [];
foreach ($collection->skip(1) as $row) { foreach ($collection->skip(1) as $row) {
$row_array = $row->toArray(); $row_array = $row->toArray();
// Skip empty rows // Skip empty rows
if (empty(array_filter($row_array))) { if (empty(array_filter($row_array))) {
continue; continue;
} }
// Ensure row has same number of columns as headers // Ensure row has same number of columns as headers
if (count($row_array) === count($headers)) { if (count($row_array) === count($headers)) {
$record = array_combine($headers, $row_array); $record = array_combine($headers, $row_array);
@@ -213,20 +214,20 @@ private function importSingleMeme(array $meme_record): bool
'save_url', // Mode: just save URL reference 'save_url', // Mode: just save URL reference
null, // Auto-generate filename null, // Auto-generate filename
'r2', // Disk (not used for URL mode) 'r2', // Disk (not used for URL mode)
trim($meme_record['name']) . " ({$format})", // Name with format trim($meme_record['name'])." ({$format})", // Name with format
null, // No specific user null, // No specific user
$config['mime'] // MIME type $config['mime'] // MIME type
); );
$media_uuids[$format . '_uuid'] = $media->uuid; $media_uuids[$format.'_uuid'] = $media->uuid;
$media_urls[$format . '_url'] = $url; $media_urls[$format.'_url'] = $url;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->command->error("Failed to create {$format} media for {$meme_record['filename']}: {$e->getMessage()}"); $this->command->error("Failed to create {$format} media for {$meme_record['filename']}: {$e->getMessage()}");
throw $e; throw $e;
} }
} }
//dump($meme_record); // dump($meme_record);
// // Generate embedding // // Generate embedding
// try { // try {
@@ -238,7 +239,7 @@ private function importSingleMeme(array $meme_record): bool
// $embedding = null; // Continue without embedding // $embedding = null; // Continue without embedding
// } // }
//dd($embedding); // dd($embedding);
// Check if record exists one more time within transaction // Check if record exists one more time within transaction
$existing_meme = MemeMedia::where('original_id', $meme_record['filename']) $existing_meme = MemeMedia::where('original_id', $meme_record['filename'])
@@ -253,12 +254,12 @@ private function importSingleMeme(array $meme_record): bool
// Check for null description before creating record // Check for null description before creating record
if ($meme_record['description'] === null) { if ($meme_record['description'] === null) {
$this->command->error("❌ NULL DESCRIPTION DETECTED:"); $this->command->error('❌ NULL DESCRIPTION DETECTED:');
$this->command->error(" Filename: {$meme_record['filename']}"); $this->command->error(" Filename: {$meme_record['filename']}");
$this->command->error(" Name: {$meme_record['name']}"); $this->command->error(" Name: {$meme_record['name']}");
$this->command->error(" Description field is NULL in CSV data"); $this->command->error(' Description field is NULL in CSV data');
$this->command->error(" CSV row data: " . json_encode($meme_record)); $this->command->error(' CSV row data: '.json_encode($meme_record));
$this->command->error("🛑 HALTING SEEDER - FIX THE CSV DATA"); $this->command->error('🛑 HALTING SEEDER - FIX THE CSV DATA');
throw new \RuntimeException("NULL description found for {$meme_record['filename']} - CSV data needs to be fixed"); throw new \RuntimeException("NULL description found for {$meme_record['filename']} - CSV data needs to be fixed");
} }
@@ -287,13 +288,13 @@ private function importSingleMeme(array $meme_record): bool
]); ]);
$meme_media->duration = MemeMediaMaintenance::getDurationUsingFfmpeg($meme_media); $meme_media->duration = MemeMediaMaintenance::getDurationUsingFfmpeg($meme_media);
//$meme_media->embedding = $embedding; // $meme_media->embedding = $embedding;
$meme_media->save(); $meme_media->save();
// Add keywords as tags // Add keywords as tags
$this->attachKeywordsAsTags($meme_media, $meme_record['keywords']); $this->attachKeywordsAsTags($meme_media, $meme_record['keywords']);
$this->command->info('✅ Imported: ' . trim($meme_record['name'])); $this->command->info('✅ Imported: '.trim($meme_record['name']));
return true; return true;
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -307,7 +308,7 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
try { try {
$meme_media->attachTags($keywords, 'meme_media'); $meme_media->attachTags($keywords, 'meme_media');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': " . $e->getMessage()); $this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': ".$e->getMessage());
Log::warning('Failed to attach tags', [ Log::warning('Failed to attach tags', [
'category_id' => $meme_media->id, 'category_id' => $meme_media->id,
'keywords' => $keywords, 'keywords' => $keywords,
@@ -321,7 +322,7 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
*/ */
private function generateCdnUrl(string $base_filename, string $extension): string private function generateCdnUrl(string $base_filename, string $extension): string
{ {
return self::CDN_BASE_URL . "/{$extension}/{$base_filename}.{$extension}"; return self::CDN_BASE_URL."/{$extension}/{$base_filename}.{$extension}";
} }
/** /**

View File

@@ -5,27 +5,28 @@ const FAQDiscord = () => {
const faqData = [ const faqData = [
{ {
q: 'How can I create a meme video?', q: 'How can I create a meme video?',
a: 'Use the video editor on top to start making your meme! Edit your caption, background and meme. Once satisfied, press the Export button to download your video!', a: 'Use the video editor on top to start making your meme!<br><br>Edit your caption, background and meme. Once satisfied, press the Export button to download your video!',
},
{
q: 'What features are available now?',
a: 'At the moment, All 200+ meme templates and 200+ backgrounds are completely free! We will be adding more memes and backgrounds to the library soon!',
}, },
{ {
q: 'Why is video export slow for me?', q: 'Why is video export slow for me?',
a: 'Video processing happens entirely in your browser using advanced web technology. Export speed depends on your video content complexity and device performance. High-end devices export quickly, while older/slower devices may take longer or even crash. If your phone is too slow, try using a faster device like a desktop computer for better performance.', a: "Video processing happens entirely in your browser using advanced web technology.<br><br> Export speed depends on your video content complexity and device performance. High-end devices export quickly, while older/slower devices may take longer or even crash. <br><br>If your phone is too slow, it's probably a potato. Try using a faster device like a desktop computer for better performance.",
},
{
q: 'What is a potato device?',
a: `Potato devices are old, dated computers, laptops, phones or tablets with little RAM and CPU power - typically devices from before ${new Date().getFullYear() - 6} (pre-${new Date().getFullYear() - 6}).<br><br>These devices are usually too weak for the modern web and may not work properly with our video editor, or may crash during video processing.`,
}, },
{ {
q: 'What video format do you export?', q: 'What video format do you export?',
a: 'We export high-quality MP4 videos optimized for all social media platforms in 9:16 format, which is compatible for TikTok, Youtube Shorts, Instagram Reels, and more.', a: 'We export high-quality MP4 videos optimized for all social media platforms in 9:16 format.<br><br>This is compatible for TikTok, Youtube Shorts, Instagram Reels, and more.',
}, },
{ {
q: 'Is there a mobile app?', q: 'Is there a mobile app?',
a: "Our web app is fully responsive and works perfectly on mobile devices. Do you want a mobile app? We'll see what can be done. Let us know in our Discord group.", a: "Our web app is fully responsive and works perfectly on mobile devices.<br><br>Do you want a mobile app? We'll see what can be done. Let us know in our Discord group.",
}, },
{ {
q: 'How often do you add new content?', q: 'How often do you add new content?',
a: 'We just started building this platform and will gradually add more meme templates and backgrounds over time, so everyone can continue using it for free with fresh content! Want a certain content? Let us know in our Discord group.', a: 'We just started building this platform and will gradually add more meme templates and backgrounds over time, so everyone can continue using it for free with fresh content!<br><br>Want a certain content? Let us know in our Discord group.',
}, },
{ {
q: 'I have more questions!', q: 'I have more questions!',
@@ -49,7 +50,9 @@ const FAQDiscord = () => {
{faqData.map((faq, index) => ( {faqData.map((faq, index) => (
<AccordionItem key={index} value={`item-${index + 1}`} className="border-b last:border-b-0"> <AccordionItem key={index} value={`item-${index + 1}`} className="border-b last:border-b-0">
<AccordionTrigger className="py-4 text-left font-medium hover:no-underline">{faq.q}</AccordionTrigger> <AccordionTrigger className="py-4 text-left font-medium hover:no-underline">{faq.q}</AccordionTrigger>
<AccordionContent className="text-muted-foreground pb-4 leading-relaxed text-balance">{faq.a}</AccordionContent> <AccordionContent className="text-muted-foreground pb-4 leading-relaxed text-balance">
<div dangerouslySetInnerHTML={{ __html: faq.a }} />
</AccordionContent>
</AccordionItem> </AccordionItem>
))} ))}
</Accordion> </Accordion>

View File

@@ -4,28 +4,31 @@ const Features = () => {
const features = [ const features = [
{ {
icon: Video, icon: Video,
title: 'Web-powered Video Editor', title: 'No installation needed',
description: 'Easy video editor with editable text, background, memes, built into the web. No additional software required.', description: 'Easy video editor with editable text, background, memes, built into the web.',
gradient: 'bg-gradient-to-br from-transparent to-blue-500/5 dark:to-blue-400/10 hover:bg-gradient-to-tl', gradient: 'bg-gradient-to-br from-transparent to-blue-500/5 dark:to-blue-400/10 hover:bg-gradient-to-tl',
order: 3,
}, },
{ {
icon: Heart, icon: Heart,
title: 'Built-in over 200+ memes, for now', title: 'Built-in over 200+ memes, for now',
description: 'Access meme and background with our editor without paying a cent.', description: 'Access meme and background with our editor without paying a cent.',
gradient: 'bg-gradient-to-br from-transparent to-pink-500/5 dark:to-pink-400/10 hover:bg-gradient-to-tl', gradient: 'bg-gradient-to-br from-transparent to-pink-500/5 dark:to-pink-400/10 hover:bg-gradient-to-tl',
order: 1,
}, },
{ {
icon: Download, icon: Download,
title: 'Export in minutes', title: 'Export in minutes',
description: 'Download high-quality 720p MP4 videos optimized for TikTok, Youtube Shorts, Instagram Reels, and more.', description: 'Download high-quality 720p MP4 videos optimized for TikTok, Youtube Shorts, Instagram Reels, and more.',
gradient: 'bg-gradient-to-br from-transparent to-green-500/5 dark:to-green-400/10 hover:bg-gradient-to-tl', gradient: 'bg-gradient-to-br from-transparent to-green-500/5 dark:to-green-400/10 hover:bg-gradient-to-tl',
order: 2,
}, },
{ {
icon: Smartphone, icon: Smartphone,
title: 'Works Everywhere', title: 'Works Everywhere',
description: 'Create on desktop, tablet, or mobile! Potato devices not recommended though.', description: 'Create on desktop, tablet, or mobile! Potato devices not recommended though.',
gradient: 'bg-gradient-to-br from-transparent to-purple-500/5 dark:to-purple-400/10 hover:bg-gradient-to-tl', gradient: 'bg-gradient-to-br from-transparent to-purple-500/5 dark:to-purple-400/10 hover:bg-gradient-to-tl',
order: 4,
}, },
{ {
icon: Library, icon: Library,
@@ -33,6 +36,7 @@ const Features = () => {
description: 'Soon we will be adding more memes and backgrounds to the library!', description: 'Soon we will be adding more memes and backgrounds to the library!',
comingSoon: true, comingSoon: true,
gradient: 'bg-gradient-to-br from-transparent to-orange-500/5 dark:to-orange-400/10 hover:bg-gradient-to-tl', gradient: 'bg-gradient-to-br from-transparent to-orange-500/5 dark:to-orange-400/10 hover:bg-gradient-to-tl',
order: 5,
}, },
]; ];
@@ -40,26 +44,28 @@ const Features = () => {
<section className=""> <section className="">
<div className="mx-auto max-w-6xl space-y-10 px-4 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl space-y-10 px-4 sm:px-6 lg:px-8">
<div className="flex flex-wrap justify-center gap-3 md:grid-cols-2 lg:grid-cols-3 lg:gap-4"> <div className="flex flex-wrap justify-center gap-3 md:grid-cols-2 lg:grid-cols-3 lg:gap-4">
{features.map((feature, index) => ( {features
<div .sort((a, b) => a.order - b.order)
key={index} .map((feature, index) => (
className={`group hover:bg-muted/50 relative h-auto min-h-[275px] w-[275px] rounded-2xl border p-6 shadow-lg ${feature.gradient} transition-all duration-300 lg:p-8`} <div
> key={index}
{feature.comingSoon && ( className={`group hover:bg-muted/50 relative h-auto min-h-[275px] w-[275px] rounded-2xl border p-6 shadow-lg ${feature.gradient} transition-all duration-300 lg:p-8`}
<div className="bg-foreground text-background absolute -top-2 -right-2 rounded-full px-2 py-1 text-xs font-medium"> >
Coming Soon! {feature.comingSoon && (
<div className="bg-foreground text-background absolute -top-2 -right-2 rounded-full px-2 py-1 text-xs font-medium">
Coming Soon!
</div>
)}
<div className="mb-4">
<feature.icon className="text-foreground h-8 w-8" />
</div> </div>
)}
<div className="mb-4"> <h3 className="text-foreground mb-2 text-xl font-semibold">{feature.title}</h3>
<feature.icon className="text-foreground h-8 w-8" />
<p className="text-muted-foreground leading-relaxed">{feature.description}</p>
</div> </div>
))}
<h3 className="text-foreground mb-2 text-xl font-semibold">{feature.title}</h3>
<p className="text-muted-foreground leading-relaxed">{feature.description}</p>
</div>
))}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -8,7 +8,7 @@ const Hero = () => {
{/* Badge */} {/* Badge */}
<div className="bg-background/50 inline-flex items-center gap-2 rounded-full border px-3 py-1 text-sm backdrop-blur-sm"> <div className="bg-background/50 inline-flex items-center gap-2 rounded-full border px-3 py-1 text-sm backdrop-blur-sm">
<div className="h-2 w-2 rounded-full bg-green-500"></div> <div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-muted-foreground">Free meme videos No signup required</span> <span className="text-muted-foreground">Instant meme videos No signup required</span>
</div> </div>
{/* Main heading */} {/* Main heading */}
@@ -19,7 +19,7 @@ const Hero = () => {
</h1> </h1>
<h2 className="text-muted-foreground mx-auto max-w-4xl text-xl leading-relaxed font-light sm:text-2xl lg:text-3xl"> <h2 className="text-muted-foreground mx-auto max-w-4xl text-xl leading-relaxed font-light sm:text-2xl lg:text-3xl">
Simple, fast, and free meme video editor Fast and simple meme video editor
</h2> </h2>
</div> </div>

File diff suppressed because one or more lines are too long