+ */
+ private function parseKeywords(?string $keywords): array
+ {
+ if (!$keywords) {
+ return [];
+ }
+
+ return array_values(array_filter(array_map(static fn ($item) => trim((string) $item), explode(',', $keywords))));
+ }
+}
diff --git a/app/Services/SeoService.php b/app/Services/SeoService.php
new file mode 100644
index 0000000..eacf170
--- /dev/null
+++ b/app/Services/SeoService.php
@@ -0,0 +1,40 @@
+seo;
+
+ $title = $seo?->title ?: $article->title;
+ $description = $seo?->description ?: ($article->excerpt ?: Str::limit(strip_tags($article->content), 160));
+
+ SEOTools::setTitle($title);
+ SEOTools::setDescription($description);
+ SEOTools::metatags()->setCanonical(route('articles.show', ['article' => $article->slug]));
+ SEOTools::metatags()->setRobots('index,follow');
+ SEOTools::opengraph()->setUrl(route('articles.show', ['article' => $article->slug]));
+ SEOTools::opengraph()->addProperty('type', 'article');
+ SEOTools::jsonLd()->setType('Article');
+ SEOTools::jsonLd()->setTitle($title);
+ SEOTools::jsonLd()->setDescription($description);
+ SEOTools::jsonLd()->setUrl(route('articles.show', ['article' => $article->slug]));
+
+ if (!empty($seo?->keywords)) {
+ $keywords = array_filter(array_map('trim', explode(',', $seo->keywords)));
+ SEOTools::metatags()->setKeywords($keywords);
+ }
+
+ if (!empty($seo?->og_image)) {
+ SEOTools::opengraph()->addImage($seo->og_image);
+ SEOTools::twitter()->setImage($seo->og_image);
+ SEOTools::jsonLd()->addImage($seo->og_image);
+ }
+ }
+}
diff --git a/app/Services/TagService.php b/app/Services/TagService.php
new file mode 100644
index 0000000..fa46ede
--- /dev/null
+++ b/app/Services/TagService.php
@@ -0,0 +1,58 @@
+tagRepository->paginateLatest($perPage);
+ }
+
+ public function create(array $data): Tag
+ {
+ $name = trim((string) ($data['name'] ?? ''));
+ $slug = $this->resolveUniqueSlug(Str::slug($name));
+
+ return $this->tagRepository->create([
+ 'name' => $name,
+ 'slug' => $slug,
+ ]);
+ }
+
+ public function update(Tag $tag, array $data): Tag
+ {
+ $name = trim((string) ($data['name'] ?? ''));
+
+ return $this->tagRepository->update($tag, [
+ 'name' => $name,
+ ]);
+ }
+
+ public function delete(Tag $tag): void
+ {
+ $this->tagRepository->delete($tag);
+ }
+
+ private function resolveUniqueSlug(string $baseSlug): string
+ {
+ $slug = $baseSlug !== '' ? $baseSlug : 'tag';
+ $counter = 1;
+
+ while (Tag::query()->where('slug', $slug)->exists()) {
+ $slug = sprintf('%s-%d', $baseSlug !== '' ? $baseSlug : 'tag', $counter++);
+ }
+
+ return $slug;
+ }
+}
diff --git a/article_hot.md b/article_hot.md
new file mode 100644
index 0000000..ca233f1
--- /dev/null
+++ b/article_hot.md
@@ -0,0 +1,34 @@
+# Logic xác định bài viết hot
+Hot Score = ViewScore + CommentScore + ShareScore + FreshScore
+Yếu tố Trọng số
+View 1
+Comment 5
+Share 8
+Fresh (bài mới) 20
+
+# Tracking view realtime
+
+# Queue job tăng view
+
+# Cron job flush Redis → DB
+
+# Job tính Hot Score
+
+# kiến trúc
+User view
+ ↓
+CDN
+ ↓
+Nginx
+ ↓
+Static HTML
+
+Track view
+ ↓
+Kafka / Redis
+ ↓
+Analytics service
+ ↓
+Hot score engine
+ ↓
+Redis ranking
\ No newline at end of file
diff --git a/bootstrap/app.php b/bootstrap/app.php
index c183276..9b2c816 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -3,6 +3,7 @@
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
+use Spatie\ResponseCache\Middlewares\CacheResponse;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
@@ -10,8 +11,13 @@
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
+ ->withCommands([
+ __DIR__.'/../app/Console/Commands',
+ ])
->withMiddleware(function (Middleware $middleware): void {
- //
+ $middleware->alias([
+ 'cacheResponse' => CacheResponse::class,
+ ]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
diff --git a/bootstrap/providers.php b/bootstrap/providers.php
index 38b258d..4e3b440 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
+ App\Providers\HorizonServiceProvider::class,
];
diff --git a/composer.json b/composer.json
index 126502d..ed560e8 100644
--- a/composer.json
+++ b/composer.json
@@ -10,8 +10,21 @@
"license": "MIT",
"require": {
"php": "^8.2",
+ "artesaos/seotools": "^1.2",
"laravel/framework": "^12.0",
- "laravel/tinker": "^2.10.1"
+ "laravel/horizon": "^5.45",
+ "laravel/sanctum": "^4.0",
+ "laravel/scout": "^10.0",
+ "laravel/tinker": "^2.10.1",
+ "meilisearch/meilisearch-php": "^1.16",
+ "predis/predis": "^2.0",
+ "spatie/laravel-activitylog": "^4.0",
+ "spatie/laravel-medialibrary": "^11.0",
+ "spatie/laravel-permission": "^7.2",
+ "spatie/laravel-responsecache": "^8.3",
+ "spatie/laravel-sitemap": "^7.0",
+ "spatie/laravel-sluggable": "^3.0",
+ "symfony/dom-crawler": "^7.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
@@ -86,4 +99,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index daf4557..d3ceb63 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,79 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c514d8f7b9fc5970bdd94287905ef584",
+ "content-hash": "7ee432cd57ffd45ab98f4da0755cb1a6",
"packages": [
+ {
+ "name": "artesaos/seotools",
+ "version": "v1.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/artesaos/seotools.git",
+ "reference": "f22bc0b0ff1bcb683ff72589d11fa80d10706597"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/artesaos/seotools/zipball/f22bc0b0ff1bcb683ff72589d11fa80d10706597",
+ "reference": "f22bc0b0ff1bcb683ff72589d11fa80d10706597",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "illuminate/config": "^10.0 || ^11.0 || ^12.0",
+ "illuminate/support": "^10.0 || ^11.0 || ^12.0",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^8.0 || ^9.0 || ^10.0",
+ "phpunit/phpunit": "^9.0 || ^10.0 || ^11.5.3"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "SEO": "Artesaos\\SEOTools\\Facades\\SEOTools",
+ "JsonLd": "Artesaos\\SEOTools\\Facades\\JsonLd",
+ "SEOMeta": "Artesaos\\SEOTools\\Facades\\SEOMeta",
+ "Twitter": "Artesaos\\SEOTools\\Facades\\TwitterCard",
+ "OpenGraph": "Artesaos\\SEOTools\\Facades\\OpenGraph"
+ },
+ "providers": [
+ "Artesaos\\SEOTools\\Providers\\SEOToolsServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Artesaos\\SEOTools\\": "src/SEOTools/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Vinicius",
+ "email": "luiz.vinicius73@gmail.com"
+ }
+ ],
+ "description": "SEO Tools for Laravel and Lumen",
+ "keywords": [
+ "JSON-LD",
+ "laravel",
+ "lumen",
+ "metatags",
+ "opengraph",
+ "seo",
+ "seotools",
+ "webmaster"
+ ],
+ "support": {
+ "issues": "https://github.com/artesaos/seotools/issues",
+ "source": "https://github.com/artesaos/seotools"
+ },
+ "time": "2025-03-07T14:44:43+00:00"
+ },
{
"name": "brick/math",
"version": "0.14.8",
@@ -135,6 +206,83 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
+ {
+ "name": "composer/semver",
+ "version": "3.4.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.11",
+ "symfony/phpunit-bridge": "^3 || ^7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/semver/issues",
+ "source": "https://github.com/composer/semver/tree/3.4.4"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-20T19:15:30+00:00"
+ },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@@ -1274,6 +1422,86 @@
},
"time": "2026-02-24T14:35:15+00:00"
},
+ {
+ "name": "laravel/horizon",
+ "version": "v5.45.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/horizon.git",
+ "reference": "637e065ae0a704288595b896ad1c7c3c9741869b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/horizon/zipball/637e065ae0a704288595b896ad1c7c3c9741869b",
+ "reference": "637e065ae0a704288595b896ad1c7c3c9741869b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcntl": "*",
+ "ext-posix": "*",
+ "illuminate/contracts": "^9.21|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/queue": "^9.21|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0",
+ "laravel/sentinel": "^1.0",
+ "nesbot/carbon": "^2.17|^3.0",
+ "php": "^8.0",
+ "ramsey/uuid": "^4.0",
+ "symfony/console": "^6.0|^7.0|^8.0",
+ "symfony/error-handler": "^6.0|^7.0|^8.0",
+ "symfony/polyfill-php83": "^1.28",
+ "symfony/process": "^6.0|^7.0|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^7.56|^8.37|^9.16|^10.9|^11.0",
+ "phpstan/phpstan": "^1.10|^2.0",
+ "predis/predis": "^1.1|^2.0|^3.0"
+ },
+ "suggest": {
+ "ext-redis": "Required to use the Redis PHP driver.",
+ "predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0|^3.0)."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Horizon": "Laravel\\Horizon\\Horizon"
+ },
+ "providers": [
+ "Laravel\\Horizon\\HorizonServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Horizon\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Dashboard and code-driven configuration for Laravel queues.",
+ "keywords": [
+ "laravel",
+ "queue"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/horizon/issues",
+ "source": "https://github.com/laravel/horizon/tree/v5.45.1"
+ },
+ "time": "2026-03-06T15:31:27+00:00"
+ },
{
"name": "laravel/prompts",
"version": "v0.3.13",
@@ -1334,38 +1562,44 @@
"time": "2026-02-06T12:17:10+00:00"
},
{
- "name": "laravel/serializable-closure",
- "version": "v2.0.10",
+ "name": "laravel/sanctum",
+ "version": "v4.3.1",
"source": {
"type": "git",
- "url": "https://github.com/laravel/serializable-closure.git",
- "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669"
+ "url": "https://github.com/laravel/sanctum.git",
+ "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669",
- "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
+ "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
"shasum": ""
},
"require": {
- "php": "^8.1"
+ "ext-json": "*",
+ "illuminate/console": "^11.0|^12.0|^13.0",
+ "illuminate/contracts": "^11.0|^12.0|^13.0",
+ "illuminate/database": "^11.0|^12.0|^13.0",
+ "illuminate/support": "^11.0|^12.0|^13.0",
+ "php": "^8.2",
+ "symfony/console": "^7.0|^8.0"
},
"require-dev": {
- "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
- "nesbot/carbon": "^2.67|^3.0",
- "pestphp/pest": "^2.36|^3.0|^4.0",
- "phpstan/phpstan": "^2.0",
- "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0"
+ "mockery/mockery": "^1.6",
+ "orchestra/testbench": "^9.15|^10.8|^11.0",
+ "phpstan/phpstan": "^1.10"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "2.x-dev"
+ "laravel": {
+ "providers": [
+ "Laravel\\Sanctum\\SanctumServiceProvider"
+ ]
}
},
"autoload": {
"psr-4": {
- "Laravel\\SerializableClosure\\": "src/"
+ "Laravel\\Sanctum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1376,65 +1610,76 @@
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
- },
- {
- "name": "Nuno Maduro",
- "email": "nuno@laravel.com"
}
],
- "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+ "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
"keywords": [
- "closure",
+ "auth",
"laravel",
- "serializable"
+ "sanctum"
],
"support": {
- "issues": "https://github.com/laravel/serializable-closure/issues",
- "source": "https://github.com/laravel/serializable-closure"
+ "issues": "https://github.com/laravel/sanctum/issues",
+ "source": "https://github.com/laravel/sanctum"
},
- "time": "2026-02-20T19:59:49+00:00"
+ "time": "2026-02-07T17:19:31+00:00"
},
{
- "name": "laravel/tinker",
- "version": "v2.11.1",
+ "name": "laravel/scout",
+ "version": "v10.24.0",
"source": {
"type": "git",
- "url": "https://github.com/laravel/tinker.git",
- "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741"
+ "url": "https://github.com/laravel/scout.git",
+ "reference": "f9864d9a727a0c0d6b95e08ed92df8c301ae6d2c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741",
- "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741",
+ "url": "https://api.github.com/repos/laravel/scout/zipball/f9864d9a727a0c0d6b95e08ed92df8c301ae6d2c",
+ "reference": "f9864d9a727a0c0d6b95e08ed92df8c301ae6d2c",
"shasum": ""
},
"require": {
- "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "php": "^7.2.5|^8.0",
- "psy/psysh": "^0.11.1|^0.12.0",
- "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0"
+ "illuminate/bus": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/database": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/http": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/pagination": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/queue": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/support": "^9.0|^10.0|^11.0|^12.0|^13.0",
+ "php": "^8.0",
+ "symfony/console": "^6.0|^7.0|^8.0"
+ },
+ "conflict": {
+ "algolia/algoliasearch-client-php": "<3.2.0|>=5.0.0"
},
"require-dev": {
- "mockery/mockery": "~1.3.3|^1.4.2",
+ "algolia/algoliasearch-client-php": "^3.2|^4.0",
+ "meilisearch/meilisearch-php": "^1.0",
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^7.31|^8.36|^9.15|^10.8|^11.0",
+ "php-http/guzzle7-adapter": "^1.0",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0"
+ "typesense/typesense-php": "^4.9.3"
},
"suggest": {
- "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)."
+ "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).",
+ "meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0).",
+ "typesense/typesense-php": "Required to use the Typesense engine (^4.9)."
},
"type": "library",
"extra": {
"laravel": {
"providers": [
- "Laravel\\Tinker\\TinkerServiceProvider"
+ "Laravel\\Scout\\ScoutServiceProvider"
]
+ },
+ "branch-alias": {
+ "dev-master": "10.x-dev"
}
},
"autoload": {
"psr-4": {
- "Laravel\\Tinker\\": "src/"
+ "Laravel\\Scout\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1447,93 +1692,278 @@
"email": "taylor@laravel.com"
}
],
- "description": "Powerful REPL for the Laravel framework.",
+ "description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
"keywords": [
- "REPL",
- "Tinker",
+ "algolia",
"laravel",
- "psysh"
+ "search"
],
"support": {
- "issues": "https://github.com/laravel/tinker/issues",
- "source": "https://github.com/laravel/tinker/tree/v2.11.1"
+ "issues": "https://github.com/laravel/scout/issues",
+ "source": "https://github.com/laravel/scout"
},
- "time": "2026-02-06T14:12:35+00:00"
+ "time": "2026-02-10T18:44:39+00:00"
},
{
- "name": "league/commonmark",
- "version": "2.8.0",
+ "name": "laravel/sentinel",
+ "version": "v1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/thephpleague/commonmark.git",
- "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb"
+ "url": "https://github.com/laravel/sentinel.git",
+ "reference": "7a98db53e0d9d6f61387f3141c07477f97425603"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
- "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+ "url": "https://api.github.com/repos/laravel/sentinel/zipball/7a98db53e0d9d6f61387f3141c07477f97425603",
+ "reference": "7a98db53e0d9d6f61387f3141c07477f97425603",
"shasum": ""
},
"require": {
- "ext-mbstring": "*",
- "league/config": "^1.1.1",
- "php": "^7.4 || ^8.0",
- "psr/event-dispatcher": "^1.0",
- "symfony/deprecation-contracts": "^2.1 || ^3.0",
- "symfony/polyfill-php80": "^1.16"
- },
- "require-dev": {
- "cebe/markdown": "^1.0",
- "commonmark/cmark": "0.31.1",
- "commonmark/commonmark.js": "0.31.1",
- "composer/package-versions-deprecated": "^1.8",
- "embed/embed": "^4.4",
- "erusev/parsedown": "^1.0",
"ext-json": "*",
- "github/gfm": "0.29.0",
- "michelf/php-markdown": "^1.4 || ^2.0",
- "nyholm/psr7": "^1.5",
- "phpstan/phpstan": "^1.8.2",
- "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
- "scrutinizer/ocular": "^1.8.1",
- "symfony/finder": "^5.3 | ^6.0 | ^7.0",
- "symfony/process": "^5.4 | ^6.0 | ^7.0",
- "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
- "unleashedtech/php-coding-standard": "^3.1.1",
- "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
+ "illuminate/container": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
+ "php": "^8.0"
},
- "suggest": {
- "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+ "require-dev": {
+ "laravel/pint": "^1.27",
+ "orchestra/testbench": "^6.47.1|^7.56|^8.37|^9.16|^10.9|^11.0",
+ "phpstan/phpstan": "^2.1.33"
},
"type": "library",
"extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Sentinel\\SentinelServiceProvider"
+ ]
+ },
"branch-alias": {
- "dev-main": "2.9-dev"
+ "dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
- "League\\CommonMark\\": "src"
+ "Laravel\\Sentinel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Colin O'Dell",
- "email": "colinodell@gmail.com",
- "homepage": "https://www.colinodell.com",
- "role": "Lead Developer"
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Mior Muhammad Zaki",
+ "email": "mior@laravel.com"
}
],
- "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
- "homepage": "https://commonmark.thephpleague.com",
- "keywords": [
- "commonmark",
- "flavored",
- "gfm",
+ "support": {
+ "source": "https://github.com/laravel/sentinel/tree/v1.0.1"
+ },
+ "time": "2026-02-12T13:32:54+00:00"
+ },
+ {
+ "name": "laravel/serializable-closure",
+ "version": "v2.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/serializable-closure.git",
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+ "nesbot/carbon": "^2.67|^3.0",
+ "pestphp/pest": "^2.36|^3.0|^4.0",
+ "phpstan/phpstan": "^2.0",
+ "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\SerializableClosure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "nuno@laravel.com"
+ }
+ ],
+ "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+ "keywords": [
+ "closure",
+ "laravel",
+ "serializable"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/serializable-closure/issues",
+ "source": "https://github.com/laravel/serializable-closure"
+ },
+ "time": "2026-02-20T19:59:49+00:00"
+ },
+ {
+ "name": "laravel/tinker",
+ "version": "v2.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/tinker.git",
+ "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741",
+ "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
+ "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
+ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
+ "php": "^7.2.5|^8.0",
+ "psy/psysh": "^0.11.1|^0.12.0",
+ "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.3|^1.4.2",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0"
+ },
+ "suggest": {
+ "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Tinker\\TinkerServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Tinker\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Powerful REPL for the Laravel framework.",
+ "keywords": [
+ "REPL",
+ "Tinker",
+ "laravel",
+ "psysh"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/tinker/issues",
+ "source": "https://github.com/laravel/tinker/tree/v2.11.1"
+ },
+ "time": "2026-02-06T14:12:35+00:00"
+ },
+ {
+ "name": "league/commonmark",
+ "version": "2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/commonmark.git",
+ "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+ "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "league/config": "^1.1.1",
+ "php": "^7.4 || ^8.0",
+ "psr/event-dispatcher": "^1.0",
+ "symfony/deprecation-contracts": "^2.1 || ^3.0",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "cebe/markdown": "^1.0",
+ "commonmark/cmark": "0.31.1",
+ "commonmark/commonmark.js": "0.31.1",
+ "composer/package-versions-deprecated": "^1.8",
+ "embed/embed": "^4.4",
+ "erusev/parsedown": "^1.0",
+ "ext-json": "*",
+ "github/gfm": "0.29.0",
+ "michelf/php-markdown": "^1.4 || ^2.0",
+ "nyholm/psr7": "^1.5",
+ "phpstan/phpstan": "^1.8.2",
+ "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
+ "scrutinizer/ocular": "^1.8.1",
+ "symfony/finder": "^5.3 | ^6.0 | ^7.0",
+ "symfony/process": "^5.4 | ^6.0 | ^7.0",
+ "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
+ "unleashedtech/php-coding-standard": "^3.1.1",
+ "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
+ },
+ "suggest": {
+ "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\CommonMark\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
+ "homepage": "https://commonmark.thephpleague.com",
+ "keywords": [
+ "commonmark",
+ "flavored",
+ "gfm",
"github",
"github-flavored",
"markdown",
@@ -2020,68 +2450,293 @@
"time": "2026-01-15T06:54:53+00:00"
},
{
- "name": "monolog/monolog",
- "version": "3.10.0",
+ "name": "maennchen/zipstream-php",
+ "version": "3.2.1",
"source": {
"type": "git",
- "url": "https://github.com/Seldaek/monolog.git",
- "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
+ "url": "https://github.com/maennchen/ZipStream-PHP.git",
+ "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
- "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5",
+ "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "psr/log": "^2.0 || ^3.0"
- },
- "provide": {
- "psr/log-implementation": "3.0.0"
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php-64bit": "^8.3"
},
"require-dev": {
- "aws/aws-sdk-php": "^3.0",
- "doctrine/couchdb": "~1.0@dev",
- "elasticsearch/elasticsearch": "^7 || ^8",
- "ext-json": "*",
- "graylog2/gelf-php": "^1.4.2 || ^2.0",
- "guzzlehttp/guzzle": "^7.4.5",
- "guzzlehttp/psr7": "^2.2",
- "mongodb/mongodb": "^1.8 || ^2.0",
- "php-amqplib/php-amqplib": "~2.4 || ^3",
- "php-console/php-console": "^3.1.8",
- "phpstan/phpstan": "^2",
- "phpstan/phpstan-deprecation-rules": "^2",
- "phpstan/phpstan-strict-rules": "^2",
- "phpunit/phpunit": "^10.5.17 || ^11.0.7",
- "predis/predis": "^1.1 || ^2",
- "rollbar/rollbar": "^4.0",
- "ruflin/elastica": "^7 || ^8",
- "symfony/mailer": "^5.4 || ^6",
- "symfony/mime": "^5.4 || ^6"
+ "brianium/paratest": "^7.7",
+ "ext-zip": "*",
+ "friendsofphp/php-cs-fixer": "^3.86",
+ "guzzlehttp/guzzle": "^7.5",
+ "mikey179/vfsstream": "^1.6",
+ "php-coveralls/php-coveralls": "^2.5",
+ "phpunit/phpunit": "^12.0",
+ "vimeo/psalm": "^6.0"
},
"suggest": {
- "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
- "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
- "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
- "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
- "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
- "ext-mbstring": "Allow to work properly with unicode symbols",
- "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
- "ext-openssl": "Required to send log messages using SSL",
- "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
- "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
- "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
- "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
- "rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ "guzzlehttp/psr7": "^2.4",
+ "psr/http-message": "^2.0"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "3.x-dev"
- }
+ "autoload": {
+ "psr-4": {
+ "ZipStream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paul Duncan",
+ "email": "pabs@pablotron.org"
+ },
+ {
+ "name": "Jonatan Männchen",
+ "email": "jonatan@maennchen.ch"
+ },
+ {
+ "name": "Jesse Donat",
+ "email": "donatj@gmail.com"
+ },
+ {
+ "name": "András Kolesár",
+ "email": "kolesar@kolesar.hu"
+ }
+ ],
+ "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+ "keywords": [
+ "stream",
+ "zip"
+ ],
+ "support": {
+ "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
+ "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/maennchen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-10T09:58:31+00:00"
+ },
+ {
+ "name": "masterminds/html5",
+ "version": "2.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "fcf91eb64359852f00d921887b219479b4f21251"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
+ "reference": "fcf91eb64359852f00d921887b219479b4f21251",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ],
+ "support": {
+ "issues": "https://github.com/Masterminds/html5-php/issues",
+ "source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
+ },
+ "time": "2025-07-25T09:04:22+00:00"
+ },
+ {
+ "name": "meilisearch/meilisearch-php",
+ "version": "v1.16.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/meilisearch/meilisearch-php.git",
+ "reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/meilisearch/meilisearch-php/zipball/f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6",
+ "reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.4 || ^8.0",
+ "php-http/discovery": "^1.7",
+ "psr/http-client": "^1.0",
+ "symfony/polyfill-php81": "^1.33"
+ },
+ "require-dev": {
+ "http-interop/http-factory-guzzle": "^1.2.0",
+ "php-cs-fixer/shim": "^3.59.3",
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.5 || ^10.5",
+ "symfony/http-client": "^5.4|^6.0|^7.0"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client",
+ "http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle",
+ "symfony/http-client": "Use Symfony Http client"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MeiliSearch\\": "src/",
+ "Meilisearch\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Clémentine Urquizar",
+ "email": "clementine@meilisearch.com"
+ },
+ {
+ "name": "Bruno Casali",
+ "email": "bruno@meilisearch.com"
+ },
+ {
+ "name": "Laurent Cazanove",
+ "email": "lau.cazanove@gmail.com"
+ },
+ {
+ "name": "Tomas Norkūnas",
+ "email": "norkunas.tom@gmail.com"
+ }
+ ],
+ "description": "PHP wrapper for the Meilisearch API",
+ "keywords": [
+ "api",
+ "client",
+ "instant",
+ "meilisearch",
+ "php",
+ "search"
+ ],
+ "support": {
+ "issues": "https://github.com/meilisearch/meilisearch-php/issues",
+ "source": "https://github.com/meilisearch/meilisearch-php/tree/v1.16.1"
+ },
+ "time": "2025-09-18T10:15:45+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "3.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "ext-json": "*",
+ "graylog2/gelf-php": "^1.4.2 || ^2.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8 || ^2.0",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+ "predis/predis": "^1.1 || ^2",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
},
"autoload": {
"psr-4": {
@@ -2385,6 +3040,60 @@
},
"time": "2026-02-13T03:05:33+00:00"
},
+ {
+ "name": "nicmart/tree",
+ "version": "0.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nicmart/Tree.git",
+ "reference": "2ef11e329d26005ef49dbacd0223bcfd2515b6cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nicmart/Tree/zipball/2ef11e329d26005ef49dbacd0223bcfd2515b6cc",
+ "reference": "2ef11e329d26005ef49dbacd0223bcfd2515b6cc",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.48.2",
+ "ergebnis/license": "^2.7.0",
+ "ergebnis/php-cs-fixer-config": "^6.28.1",
+ "fakerphp/faker": "^1.24.1",
+ "infection/infection": "~0.26.19",
+ "phpunit/phpunit": "^9.6.19",
+ "psalm/plugin-phpunit": "~0.19.0",
+ "vimeo/psalm": "^5.26.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Tree\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolò Martini",
+ "email": "nicmartnic@gmail.com"
+ },
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com"
+ }
+ ],
+ "description": "A basic but flexible php tree data structure and a fluent tree builder implementation.",
+ "support": {
+ "issues": "https://github.com/nicmart/Tree/issues",
+ "source": "https://github.com/nicmart/Tree/tree/0.10.1"
+ },
+ "time": "2025-11-25T08:51:01+00:00"
+ },
{
"name": "nikic/php-parser",
"version": "v5.7.0",
@@ -2530,6 +3239,85 @@
],
"time": "2026-02-16T23:10:27+00:00"
},
+ {
+ "name": "php-http/discovery",
+ "version": "1.20.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-http/discovery.git",
+ "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
+ "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0|^2.0",
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "nyholm/psr7": "<1.0",
+ "zendframework/zend-diactoros": "*"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "*",
+ "psr/http-factory-implementation": "*",
+ "psr/http-message-implementation": "*"
+ },
+ "require-dev": {
+ "composer/composer": "^1.0.2|^2.0",
+ "graham-campbell/phpspec-skip-example-extension": "^5.0",
+ "php-http/httplug": "^1.0 || ^2.0",
+ "php-http/message-factory": "^1.0",
+ "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
+ "sebastian/comparator": "^3.0.5 || ^4.0.8",
+ "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Http\\Discovery\\Composer\\Plugin",
+ "plugin-optional": true
+ },
+ "autoload": {
+ "psr-4": {
+ "Http\\Discovery\\": "src/"
+ },
+ "exclude-from-classmap": [
+ "src/Composer/Plugin.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com"
+ }
+ ],
+ "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
+ "homepage": "http://php-http.org",
+ "keywords": [
+ "adapter",
+ "client",
+ "discovery",
+ "factory",
+ "http",
+ "message",
+ "psr17",
+ "psr7"
+ ],
+ "support": {
+ "issues": "https://github.com/php-http/discovery/issues",
+ "source": "https://github.com/php-http/discovery/tree/1.20.0"
+ },
+ "time": "2024-10-02T11:20:13+00:00"
+ },
{
"name": "phpoption/phpoption",
"version": "1.9.5",
@@ -2606,17 +3394,79 @@
"time": "2025-12-27T19:41:33+00:00"
},
{
- "name": "psr/clock",
- "version": "1.0.0",
+ "name": "predis/predis",
+ "version": "v2.4.1",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/clock.git",
- "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ "url": "https://github.com/predis/predis.git",
+ "reference": "07105e050622ed80bd60808367ced9e379f31530"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
- "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "url": "https://api.github.com/repos/predis/predis/zipball/07105e050622ed80bd60808367ced9e379f31530",
+ "reference": "07105e050622ed80bd60808367ced9e379f31530",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.3",
+ "phpstan/phpstan": "^1.9",
+ "phpunit/phpcov": "^6.0 || ^8.0",
+ "phpunit/phpunit": "^8.0 || ^9.4"
+ },
+ "suggest": {
+ "ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Predis\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Till Krüss",
+ "homepage": "https://till.im",
+ "role": "Maintainer"
+ }
+ ],
+ "description": "A flexible and feature-complete Redis/Valkey client for PHP.",
+ "homepage": "http://github.com/predis/predis",
+ "keywords": [
+ "nosql",
+ "predis",
+ "redis"
+ ],
+ "support": {
+ "issues": "https://github.com/predis/predis/issues",
+ "source": "https://github.com/predis/predis/tree/v2.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/tillkruss",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-12T18:00:11+00:00"
+ },
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
"shasum": ""
},
"require": {
@@ -3141,53 +3991,1073 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
- "name": "ramsey/collection",
- "version": "2.1.1",
+ "name": "ramsey/collection",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/collection.git",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "captainhook/plugin-composer": "^5.3",
+ "ergebnis/composer-normalize": "^2.45",
+ "fakerphp/faker": "^1.24",
+ "hamcrest/hamcrest-php": "^2.0",
+ "jangregor/phpstan-prophecy": "^2.1",
+ "mockery/mockery": "^1.6",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpspec/prophecy-phpunit": "^2.3",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5",
+ "ramsey/coding-standard": "^2.3",
+ "ramsey/conventional-commits": "^1.6",
+ "roave/security-advisories": "dev-latest"
+ },
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ },
+ "ramsey/conventional-commits": {
+ "configFile": "conventional-commits.json"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "A PHP library for representing and manipulating collections.",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/collection/issues",
+ "source": "https://github.com/ramsey/collection/tree/2.1.1"
+ },
+ "time": "2025-03-22T05:38:12+00:00"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "4.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
+ "reference": "8429c78ca35a09f27565311b98101e2826affde0",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
+ "php": "^8.0",
+ "ramsey/collection": "^1.2 || ^2.0"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.25",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "ergebnis/composer-normalize": "^2.47",
+ "mockery/mockery": "^1.6",
+ "paragonie/random-lib": "^2",
+ "php-mock/php-mock": "^2.6",
+ "php-mock/php-mock-mockery": "^1.5",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpbench/phpbench": "^1.2.14",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "slevomat/coding-standard": "^8.18",
+ "squizlabs/php_codesniffer": "^3.13"
+ },
+ "suggest": {
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "type": "library",
+ "extra": {
+ "captainhook": {
+ "force-install": true
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/uuid/issues",
+ "source": "https://github.com/ramsey/uuid/tree/4.9.2"
+ },
+ "time": "2025-12-14T04:43:48+00:00"
+ },
+ {
+ "name": "spatie/browsershot",
+ "version": "5.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/browsershot.git",
+ "reference": "d2e4ac7c69162999940172a674bf83ddc5ac59ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/browsershot/zipball/d2e4ac7c69162999940172a674bf83ddc5ac59ea",
+ "reference": "d2e4ac7c69162999940172a674bf83ddc5ac59ea",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "ext-json": "*",
+ "php": "^8.2",
+ "spatie/temporary-directory": "^2.0",
+ "symfony/process": "^6.0|^7.0|^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^3.0|^4.0",
+ "spatie/image": "^3.6",
+ "spatie/pdf-to-text": "^1.52",
+ "spatie/phpunit-snapshot-assertions": "^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Browsershot\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://github.com/freekmurze",
+ "role": "Developer"
+ }
+ ],
+ "description": "Convert a webpage to an image or pdf using headless Chrome",
+ "homepage": "https://github.com/spatie/browsershot",
+ "keywords": [
+ "chrome",
+ "convert",
+ "headless",
+ "image",
+ "pdf",
+ "puppeteer",
+ "screenshot",
+ "webpage"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/browsershot/tree/5.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-18T16:10:58+00:00"
+ },
+ {
+ "name": "spatie/crawler",
+ "version": "8.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/crawler.git",
+ "reference": "18198a2198adff1637c0028cb60d2c9559721556"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/crawler/zipball/18198a2198adff1637c0028cb60d2c9559721556",
+ "reference": "18198a2198adff1637c0028cb60d2c9559721556",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^7.3",
+ "guzzlehttp/psr7": "^2.0",
+ "illuminate/collections": "^10.0|^11.0|^12.0|^13.0",
+ "nicmart/tree": "^0.10",
+ "php": "^8.2",
+ "spatie/browsershot": "^5.0.5",
+ "spatie/robots-txt": "^2.0",
+ "symfony/dom-crawler": "^6.0|^7.0|^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^2.0|^3.0|^4.0",
+ "spatie/ray": "^1.37"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Crawler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be"
+ }
+ ],
+ "description": "Crawl all internal links found on a website",
+ "homepage": "https://github.com/spatie/crawler",
+ "keywords": [
+ "crawler",
+ "link",
+ "spatie",
+ "website"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/crawler/issues",
+ "source": "https://github.com/spatie/crawler/tree/8.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-21T22:52:37+00:00"
+ },
+ {
+ "name": "spatie/image",
+ "version": "3.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/image.git",
+ "reference": "1ea40e429587df64b34139e6b18af34cb20ae5b9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/image/zipball/1ea40e429587df64b34139e6b18af34cb20ae5b9",
+ "reference": "1ea40e429587df64b34139e6b18af34cb20ae5b9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-exif": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": "^8.2",
+ "spatie/image-optimizer": "^1.7.5",
+ "spatie/temporary-directory": "^2.2",
+ "symfony/process": "^6.4|^7.0|^8.0"
+ },
+ "require-dev": {
+ "ext-gd": "*",
+ "ext-imagick": "*",
+ "laravel/sail": "^1.34",
+ "pestphp/pest": "^3.0|^4.0",
+ "phpstan/phpstan": "^1.10.50",
+ "spatie/pest-plugin-snapshots": "^2.1",
+ "spatie/pixelmatch-php": "^1.0",
+ "spatie/ray": "^1.40.1",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Image\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Manipulate images with an expressive API",
+ "homepage": "https://github.com/spatie/image",
+ "keywords": [
+ "image",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/image/tree/3.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-03-01T20:58:11+00:00"
+ },
+ {
+ "name": "spatie/image-optimizer",
+ "version": "1.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/image-optimizer.git",
+ "reference": "2ad9ac7c19501739183359ae64ea6c15869c23d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/2ad9ac7c19501739183359ae64ea6c15869c23d9",
+ "reference": "2ad9ac7c19501739183359ae64ea6c15869c23d9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "php": "^7.3|^8.0",
+ "psr/log": "^1.0 | ^2.0 | ^3.0",
+ "symfony/process": "^4.2|^5.0|^6.0|^7.0|^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^1.21|^2.0|^3.0|^4.0",
+ "phpunit/phpunit": "^8.5.21|^9.4.4|^10.0|^11.0|^12.0",
+ "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\ImageOptimizer\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Easily optimize images using PHP",
+ "homepage": "https://github.com/spatie/image-optimizer",
+ "keywords": [
+ "image-optimizer",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/image-optimizer/issues",
+ "source": "https://github.com/spatie/image-optimizer/tree/1.8.1"
+ },
+ "time": "2025-11-26T10:57:19+00:00"
+ },
+ {
+ "name": "spatie/laravel-activitylog",
+ "version": "4.12.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-activitylog.git",
+ "reference": "bf66b5bbe9a946e977e876420d16b30b9aff1b2d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/bf66b5bbe9a946e977e876420d16b30b9aff1b2d",
+ "reference": "bf66b5bbe9a946e977e876420d16b30b9aff1b2d",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
+ "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
+ "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
+ "php": "^8.1",
+ "spatie/laravel-package-tools": "^1.6.3"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.6 || ^10.0 || ^11.0",
+ "pestphp/pest": "^1.20 || ^2.0 || ^3.0 || ^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Activitylog\\ActivitylogServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\Activitylog\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian De Deyne",
+ "email": "sebastian@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Tom Witkowski",
+ "email": "dev.gummibeer@gmail.com",
+ "homepage": "https://gummibeer.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A very simple activity logger to monitor the users of your website or application",
+ "homepage": "https://github.com/spatie/activitylog",
+ "keywords": [
+ "activity",
+ "laravel",
+ "log",
+ "spatie",
+ "user"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-activitylog/issues",
+ "source": "https://github.com/spatie/laravel-activitylog/tree/4.12.1"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-22T08:37:18+00:00"
+ },
+ {
+ "name": "spatie/laravel-medialibrary",
+ "version": "11.21.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-medialibrary.git",
+ "reference": "d6e2595033ffd130d4dd5d124510ab3304794c44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/d6e2595033ffd130d4dd5d124510ab3304794c44",
+ "reference": "d6e2595033ffd130d4dd5d124510ab3304794c44",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "^3.4",
+ "ext-exif": "*",
+ "ext-fileinfo": "*",
+ "ext-json": "*",
+ "illuminate/bus": "^10.2|^11.0|^12.0|^13.0",
+ "illuminate/conditionable": "^10.2|^11.0|^12.0|^13.0",
+ "illuminate/console": "^10.2|^11.0|^12.0|^13.0",
+ "illuminate/database": "^10.2|^11.0|^12.0|^13.0",
+ "illuminate/pipeline": "^10.2|^11.0|^12.0|^13.0",
+ "illuminate/support": "^10.2|^11.0|^12.0|^13.0",
+ "maennchen/zipstream-php": "^3.1",
+ "php": "^8.2",
+ "spatie/image": "^3.3.2",
+ "spatie/laravel-package-tools": "^1.16.1",
+ "spatie/temporary-directory": "^2.2",
+ "symfony/console": "^6.4.1|^7.0|^8.0"
+ },
+ "conflict": {
+ "php-ffmpeg/php-ffmpeg": "<0.6.1"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.293.10",
+ "ext-imagick": "*",
+ "ext-pdo_sqlite": "*",
+ "ext-zip": "*",
+ "guzzlehttp/guzzle": "^7.8.1",
+ "larastan/larastan": "^2.7|^3.0",
+ "league/flysystem-aws-s3-v3": "^3.22",
+ "mockery/mockery": "^1.6.7",
+ "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0",
+ "pestphp/pest": "^2.36|^3.0|^4.0",
+ "phpstan/extension-installer": "^1.3.1",
+ "spatie/laravel-ray": "^1.33",
+ "spatie/pdf-to-image": "^2.2|^3.0",
+ "spatie/pest-expectations": "^1.13",
+ "spatie/pest-plugin-snapshots": "^2.1"
+ },
+ "suggest": {
+ "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage",
+ "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails",
+ "spatie/pdf-to-image": "Required for generating thumbnails of PDFs and SVGs"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\MediaLibrary\\MediaLibraryServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\MediaLibrary\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Associate files with Eloquent models",
+ "homepage": "https://github.com/spatie/laravel-medialibrary",
+ "keywords": [
+ "cms",
+ "conversion",
+ "downloads",
+ "images",
+ "laravel",
+ "laravel-medialibrary",
+ "media",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-medialibrary/issues",
+ "source": "https://github.com/spatie/laravel-medialibrary/tree/11.21.0"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-21T15:58:56+00:00"
+ },
+ {
+ "name": "spatie/laravel-package-tools",
+ "version": "1.93.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-package-tools.git",
+ "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
+ "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.5",
+ "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0",
+ "pestphp/pest": "^2.1|^3.1|^4.0",
+ "phpunit/php-code-coverage": "^10.0|^11.0|^12.0",
+ "phpunit/phpunit": "^10.5|^11.5|^12.5",
+ "spatie/pest-plugin-test-time": "^2.2|^3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\LaravelPackageTools\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Tools for creating Laravel packages",
+ "homepage": "https://github.com/spatie/laravel-package-tools",
+ "keywords": [
+ "laravel-package-tools",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-package-tools/issues",
+ "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-21T12:49:54+00:00"
+ },
+ {
+ "name": "spatie/laravel-permission",
+ "version": "7.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-permission.git",
+ "reference": "062b0cd8e3a1753fa7a53e468b918710004aa06b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/062b0cd8e3a1753fa7a53e468b918710004aa06b",
+ "reference": "062b0cd8e3a1753fa7a53e468b918710004aa06b",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/auth": "^12.0|^13.0",
+ "illuminate/container": "^12.0|^13.0",
+ "illuminate/contracts": "^12.0|^13.0",
+ "illuminate/database": "^12.0|^13.0",
+ "php": "^8.4",
+ "spatie/laravel-package-tools": "^1.0"
+ },
+ "require-dev": {
+ "larastan/larastan": "^3.9",
+ "laravel/passport": "^13.0",
+ "laravel/pint": "^1.0",
+ "orchestra/testbench": "^10.0|^11.0",
+ "pestphp/pest": "^3.0|^4.0",
+ "pestphp/pest-plugin-laravel": "^3.0|^4.1",
+ "phpstan/phpstan": "^2.1"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Permission\\PermissionServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "7.x-dev",
+ "dev-master": "7.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\Permission\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Permission handling for Laravel 12 and up",
+ "homepage": "https://github.com/spatie/laravel-permission",
+ "keywords": [
+ "acl",
+ "laravel",
+ "permission",
+ "permissions",
+ "rbac",
+ "roles",
+ "security",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-permission/issues",
+ "source": "https://github.com/spatie/laravel-permission/tree/7.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-23T20:30:07+00:00"
+ },
+ {
+ "name": "spatie/laravel-responsecache",
+ "version": "8.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-responsecache.git",
+ "reference": "7f5be36713076d3164949f21768b53b236eecf03"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-responsecache/zipball/7f5be36713076d3164949f21768b53b236eecf03",
+ "reference": "7f5be36713076d3164949f21768b53b236eecf03",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/cache": "^12.0|^13.0",
+ "illuminate/console": "^12.0|^13.0",
+ "illuminate/container": "^12.0|^13.0",
+ "illuminate/http": "^12.0|^13.0",
+ "illuminate/support": "^12.0|^13.0",
+ "nesbot/carbon": "^3.0",
+ "php": "^8.4",
+ "spatie/laravel-package-tools": "^1.9",
+ "spatie/php-attribute-reader": "^1.0"
+ },
+ "require-dev": {
+ "larastan/larastan": "^3.9",
+ "laravel/framework": "^12.0|^13.0",
+ "laravel/pint": "^1.13.7",
+ "mockery/mockery": "^1.6",
+ "orchestra/testbench": "^10.0|^11.0",
+ "pestphp/pest": "^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "ResponseCache": "Spatie\\ResponseCache\\Facades\\ResponseCache"
+ },
+ "providers": [
+ "Spatie\\ResponseCache\\ResponseCacheServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\ResponseCache\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Speed up a Laravel application by caching the entire response",
+ "homepage": "https://github.com/spatie/laravel-responsecache",
+ "keywords": [
+ "cache",
+ "laravel",
+ "laravel-responsecache",
+ "performance",
+ "response",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-responsecache/issues",
+ "source": "https://github.com/spatie/laravel-responsecache/tree/8.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-03-03T19:51:38+00:00"
+ },
+ {
+ "name": "spatie/laravel-sitemap",
+ "version": "7.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-sitemap.git",
+ "reference": "52397c6f4219a8a0a163ddb8a8d5bc5f9bba0df4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-sitemap/zipball/52397c6f4219a8a0a163ddb8a8d5bc5f9bba0df4",
+ "reference": "52397c6f4219a8a0a163ddb8a8d5bc5f9bba0df4",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^7.8",
+ "illuminate/support": "^11.0|^12.0||^13.0",
+ "nesbot/carbon": "^2.71|^3.0",
+ "php": "^8.2||^8.3||^8.4||^8.5",
+ "spatie/crawler": "^8.0.1",
+ "spatie/laravel-package-tools": "^1.16.1",
+ "symfony/dom-crawler": "^6.3.4|^7.0|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.6.6",
+ "orchestra/testbench": "^9.0|^10.0||^11.0",
+ "pestphp/pest": "^3.7.4|^4.0",
+ "spatie/pest-plugin-snapshots": "^2.1",
+ "spatie/phpunit-snapshot-assertions": "^5.1.2",
+ "spatie/temporary-directory": "^2.2"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Sitemap\\SitemapServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Sitemap\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Create and generate sitemaps with ease",
+ "homepage": "https://github.com/spatie/laravel-sitemap",
+ "keywords": [
+ "laravel-sitemap",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/laravel-sitemap/tree/7.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ }
+ ],
+ "time": "2026-02-21T23:00:46+00:00"
+ },
+ {
+ "name": "spatie/laravel-sluggable",
+ "version": "3.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-sluggable.git",
+ "reference": "8050bf38a6e03ac4974e1f7bc722983242abfae0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-sluggable/zipball/8050bf38a6e03ac4974e1f7bc722983242abfae0",
+ "reference": "8050bf38a6e03ac4974e1f7bc722983242abfae0",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/database": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+ "php": "^8.2"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0",
+ "pestphp/pest": "^2.0|^3.7|^4.0",
+ "spatie/laravel-translatable": "^5.0|^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Sluggable\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Generate slugs when saving Eloquent models",
+ "homepage": "https://github.com/spatie/laravel-sluggable",
+ "keywords": [
+ "laravel-sluggable",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/laravel-sluggable/tree/3.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-21T14:25:10+00:00"
+ },
+ {
+ "name": "spatie/php-attribute-reader",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/php-attribute-reader.git",
+ "reference": "46e7484d7b51f5b22d672745c541e48c5a385404"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/php-attribute-reader/zipball/46e7484d7b51f5b22d672745c541e48c5a385404",
+ "reference": "46e7484d7b51f5b22d672745c541e48c5a385404",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^1.0|^2.0|^3.0|^4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Attributes\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A clean API for working with PHP attributes",
+ "homepage": "https://github.com/spatie/php-attribute-reader",
+ "keywords": [
+ "attributes",
+ "php-attribute-reader",
+ "reflection",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/php-attribute-reader/issues",
+ "source": "https://github.com/spatie/php-attribute-reader/tree/1.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-23T09:01:55+00:00"
+ },
+ {
+ "name": "spatie/robots-txt",
+ "version": "2.5.4",
"source": {
"type": "git",
- "url": "https://github.com/ramsey/collection.git",
- "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+ "url": "https://github.com/spatie/robots-txt.git",
+ "reference": "a8dd35d0a94e863f52509a366a634978e9c1db03"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
- "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "url": "https://api.github.com/repos/spatie/robots-txt/zipball/a8dd35d0a94e863f52509a366a634978e9c1db03",
+ "reference": "a8dd35d0a94e863f52509a366a634978e9c1db03",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
- "captainhook/plugin-composer": "^5.3",
- "ergebnis/composer-normalize": "^2.45",
- "fakerphp/faker": "^1.24",
- "hamcrest/hamcrest-php": "^2.0",
- "jangregor/phpstan-prophecy": "^2.1",
- "mockery/mockery": "^1.6",
- "php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.4",
- "phpspec/prophecy-phpunit": "^2.3",
- "phpstan/extension-installer": "^1.4",
- "phpstan/phpstan": "^2.1",
- "phpstan/phpstan-mockery": "^2.0",
- "phpstan/phpstan-phpunit": "^2.0",
- "phpunit/phpunit": "^10.5",
- "ramsey/coding-standard": "^2.3",
- "ramsey/conventional-commits": "^1.6",
- "roave/security-advisories": "dev-latest"
+ "phpunit/phpunit": "^11.5.2"
},
"type": "library",
- "extra": {
- "captainhook": {
- "force-install": true
- },
- "ramsey/conventional-commits": {
- "configFile": "conventional-commits.json"
- }
- },
"autoload": {
"psr-4": {
- "Ramsey\\Collection\\": "src/"
+ "Spatie\\Robots\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -3196,103 +5066,94 @@
],
"authors": [
{
- "name": "Ben Ramsey",
- "email": "ben@benramsey.com",
- "homepage": "https://benramsey.com"
+ "name": "Brent Roose",
+ "email": "brent@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
}
],
- "description": "A PHP library for representing and manipulating collections.",
+ "description": "Determine if a page may be crawled from robots.txt and robots meta tags",
+ "homepage": "https://github.com/spatie/robots-txt",
"keywords": [
- "array",
- "collection",
- "hash",
- "map",
- "queue",
- "set"
+ "robots-txt",
+ "spatie"
],
"support": {
- "issues": "https://github.com/ramsey/collection/issues",
- "source": "https://github.com/ramsey/collection/tree/2.1.1"
+ "issues": "https://github.com/spatie/robots-txt/issues",
+ "source": "https://github.com/spatie/robots-txt/tree/2.5.4"
},
- "time": "2025-03-22T05:38:12+00:00"
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-25T07:59:20+00:00"
},
{
- "name": "ramsey/uuid",
- "version": "4.9.2",
+ "name": "spatie/temporary-directory",
+ "version": "2.3.1",
"source": {
"type": "git",
- "url": "https://github.com/ramsey/uuid.git",
- "reference": "8429c78ca35a09f27565311b98101e2826affde0"
+ "url": "https://github.com/spatie/temporary-directory.git",
+ "reference": "662e481d6ec07ef29fd05010433428851a42cd07"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
- "reference": "8429c78ca35a09f27565311b98101e2826affde0",
+ "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07",
+ "reference": "662e481d6ec07ef29fd05010433428851a42cd07",
"shasum": ""
},
"require": {
- "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
- "php": "^8.0",
- "ramsey/collection": "^1.2 || ^2.0"
- },
- "replace": {
- "rhumsaa/uuid": "self.version"
+ "php": "^8.0"
},
"require-dev": {
- "captainhook/captainhook": "^5.25",
- "captainhook/plugin-composer": "^5.3",
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
- "ergebnis/composer-normalize": "^2.47",
- "mockery/mockery": "^1.6",
- "paragonie/random-lib": "^2",
- "php-mock/php-mock": "^2.6",
- "php-mock/php-mock-mockery": "^1.5",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpbench/phpbench": "^1.2.14",
- "phpstan/extension-installer": "^1.4",
- "phpstan/phpstan": "^2.1",
- "phpstan/phpstan-mockery": "^2.0",
- "phpstan/phpstan-phpunit": "^2.0",
- "phpunit/phpunit": "^9.6",
- "slevomat/coding-standard": "^8.18",
- "squizlabs/php_codesniffer": "^3.13"
- },
- "suggest": {
- "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
- "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
- "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
- "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
- "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ "phpunit/phpunit": "^9.5"
},
"type": "library",
- "extra": {
- "captainhook": {
- "force-install": true
- }
- },
"autoload": {
- "files": [
- "src/functions.php"
- ],
"psr-4": {
- "Ramsey\\Uuid\\": "src/"
+ "Spatie\\TemporaryDirectory\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "authors": [
+ {
+ "name": "Alex Vanderbist",
+ "email": "alex@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Easily create, use and destroy temporary directories",
+ "homepage": "https://github.com/spatie/temporary-directory",
"keywords": [
- "guid",
- "identifier",
- "uuid"
+ "php",
+ "spatie",
+ "temporary-directory"
],
"support": {
- "issues": "https://github.com/ramsey/uuid/issues",
- "source": "https://github.com/ramsey/uuid/tree/4.9.2"
+ "issues": "https://github.com/spatie/temporary-directory/issues",
+ "source": "https://github.com/spatie/temporary-directory/tree/2.3.1"
},
- "time": "2025-12-14T04:43:48+00:00"
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2026-01-12T07:42:22+00:00"
},
{
"name": "symfony/clock",
@@ -3605,6 +5466,78 @@
],
"time": "2024-09-25T14:21:43+00:00"
},
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e",
+ "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e",
+ "shasum": ""
+ },
+ "require": {
+ "masterminds/html5": "^2.6",
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases DOM navigation for HTML and XML documents",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "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": "2026-02-17T07:53:42+00:00"
+ },
{
"name": "symfony/error-handler",
"version": "v7.4.4",
@@ -4796,6 +6729,86 @@
],
"time": "2025-01-02T08:10:11+00:00"
},
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "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-09T11:45:10+00:00"
+ },
{
"name": "symfony/polyfill-php83",
"version": "v1.33.0",
diff --git a/config/activitylog.php b/config/activitylog.php
new file mode 100644
index 0000000..f1262f5
--- /dev/null
+++ b/config/activitylog.php
@@ -0,0 +1,52 @@
+ env('ACTIVITY_LOGGER_ENABLED', true),
+
+ /*
+ * When the clean-command is executed, all recording activities older than
+ * the number of days specified here will be deleted.
+ */
+ 'delete_records_older_than_days' => 365,
+
+ /*
+ * If no log name is passed to the activity() helper
+ * we use this default log name.
+ */
+ 'default_log_name' => 'default',
+
+ /*
+ * You can specify an auth driver here that gets user models.
+ * If this is null we'll use the current Laravel auth driver.
+ */
+ 'default_auth_driver' => null,
+
+ /*
+ * If set to true, the subject returns soft deleted models.
+ */
+ 'subject_returns_soft_deleted_models' => false,
+
+ /*
+ * This model will be used to log activity.
+ * It should implement the Spatie\Activitylog\Contracts\Activity interface
+ * and extend Illuminate\Database\Eloquent\Model.
+ */
+ 'activity_model' => \Spatie\Activitylog\Models\Activity::class,
+
+ /*
+ * This is the name of the table that will be created by the migration and
+ * used by the Activity model shipped with this package.
+ */
+ 'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
+
+ /*
+ * This is the database connection that will be used by the migration and
+ * the Activity model shipped with this package. In case it's not set
+ * Laravel's database.default will be used instead.
+ */
+ 'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
+];
diff --git a/config/horizon.php b/config/horizon.php
new file mode 100644
index 0000000..32f8f0d
--- /dev/null
+++ b/config/horizon.php
@@ -0,0 +1,254 @@
+ env('HORIZON_NAME'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Horizon Domain
+ |--------------------------------------------------------------------------
+ |
+ | This is the subdomain where Horizon will be accessible from. If this
+ | setting is null, Horizon will reside under the same domain as the
+ | application. Otherwise, this value will serve as the subdomain.
+ |
+ */
+
+ 'domain' => env('HORIZON_DOMAIN'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Horizon Path
+ |--------------------------------------------------------------------------
+ |
+ | This is the URI path where Horizon will be accessible from. Feel free
+ | to change this path to anything you like. Note that the URI will not
+ | affect the paths of its internal API that aren't exposed to users.
+ |
+ */
+
+ 'path' => env('HORIZON_PATH', 'horizon'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Horizon Redis Connection
+ |--------------------------------------------------------------------------
+ |
+ | This is the name of the Redis connection where Horizon will store the
+ | meta information required for it to function. It includes the list
+ | of supervisors, failed jobs, job metrics, and other information.
+ |
+ */
+
+ 'use' => 'default',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Horizon Redis Prefix
+ |--------------------------------------------------------------------------
+ |
+ | This prefix will be used when storing all Horizon data in Redis. You
+ | may modify the prefix when you are running multiple installations
+ | of Horizon on the same server so that they don't have problems.
+ |
+ */
+
+ 'prefix' => env(
+ 'HORIZON_PREFIX',
+ Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Horizon Route Middleware
+ |--------------------------------------------------------------------------
+ |
+ | These middleware will get attached onto each Horizon route, giving you
+ | the chance to add your own middleware to this list or change any of
+ | the existing middleware. Or, you can simply stick with this list.
+ |
+ */
+
+ 'middleware' => ['web'],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Wait Time Thresholds
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to configure when the LongWaitDetected event
+ | will be fired. Every connection / queue combination may have its
+ | own, unique threshold (in seconds) before this event is fired.
+ |
+ */
+
+ 'waits' => [
+ 'redis:default' => 60,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Job Trimming Times
+ |--------------------------------------------------------------------------
+ |
+ | Here you can configure for how long (in minutes) you desire Horizon to
+ | persist the recent and failed jobs. Typically, recent jobs are kept
+ | for one hour while all failed jobs are stored for an entire week.
+ |
+ */
+
+ 'trim' => [
+ 'recent' => 60,
+ 'pending' => 60,
+ 'completed' => 60,
+ 'recent_failed' => 10080,
+ 'failed' => 10080,
+ 'monitored' => 10080,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Silenced Jobs
+ |--------------------------------------------------------------------------
+ |
+ | Silencing a job will instruct Horizon to not place the job in the list
+ | of completed jobs within the Horizon dashboard. This setting may be
+ | used to fully remove any noisy jobs from the completed jobs list.
+ |
+ */
+
+ 'silenced' => [
+ // App\Jobs\ExampleJob::class,
+ ],
+
+ 'silenced_tags' => [
+ // 'notifications',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Metrics
+ |--------------------------------------------------------------------------
+ |
+ | Here you can configure how many snapshots should be kept to display in
+ | the metrics graph. This will get used in combination with Horizon's
+ | `horizon:snapshot` schedule to define how long to retain metrics.
+ |
+ */
+
+ 'metrics' => [
+ 'trim_snapshots' => [
+ 'job' => 24,
+ 'queue' => 24,
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Fast Termination
+ |--------------------------------------------------------------------------
+ |
+ | When this option is enabled, Horizon's "terminate" command will not
+ | wait on all of the workers to terminate unless the --wait option
+ | is provided. Fast termination can shorten deployment delay by
+ | allowing a new instance of Horizon to start while the last
+ | instance will continue to terminate each of its workers.
+ |
+ */
+
+ 'fast_termination' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Memory Limit (MB)
+ |--------------------------------------------------------------------------
+ |
+ | This value describes the maximum amount of memory the Horizon master
+ | supervisor may consume before it is terminated and restarted. For
+ | configuring these limits on your workers, see the next section.
+ |
+ */
+
+ 'memory_limit' => 64,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Worker Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define the queue worker settings used by your application
+ | in all environments. These supervisors and settings handle all your
+ | queued jobs and will be provisioned by Horizon during deployment.
+ |
+ */
+
+ 'defaults' => [
+ 'supervisor-1' => [
+ 'connection' => 'redis',
+ 'queue' => ['default'],
+ 'balance' => 'auto',
+ 'autoScalingStrategy' => 'time',
+ 'maxProcesses' => 1,
+ 'maxTime' => 0,
+ 'maxJobs' => 0,
+ 'memory' => 128,
+ 'tries' => 1,
+ 'timeout' => 60,
+ 'nice' => 0,
+ ],
+ ],
+
+ 'environments' => [
+ 'production' => [
+ 'supervisor-1' => [
+ 'maxProcesses' => 10,
+ 'balanceMaxShift' => 1,
+ 'balanceCooldown' => 3,
+ ],
+ ],
+
+ 'local' => [
+ 'supervisor-1' => [
+ 'maxProcesses' => 3,
+ ],
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | File Watcher Configuration
+ |--------------------------------------------------------------------------
+ |
+ | The following list of directories and files will be watched when using
+ | the `horizon:listen` command. Whenever any directories or files are
+ | changed, Horizon will automatically restart to apply all changes.
+ |
+ */
+
+ 'watch' => [
+ 'app',
+ 'bootstrap',
+ 'config/**/*.php',
+ 'database/**/*.php',
+ 'public/**/*.php',
+ 'resources/**/*.php',
+ 'routes',
+ 'composer.lock',
+ 'composer.json',
+ '.env',
+ ],
+];
diff --git a/config/media-library.php b/config/media-library.php
new file mode 100644
index 0000000..4439c20
--- /dev/null
+++ b/config/media-library.php
@@ -0,0 +1,303 @@
+ env('MEDIA_DISK', 'public'),
+
+ /*
+ * The maximum file size of an item in bytes.
+ * Adding a larger file will result in an exception.
+ */
+ 'max_file_size' => 1024 * 1024 * 10, // 10MB
+
+ /*
+ * This queue connection will be used to generate derived and responsive images.
+ * Leave empty to use the default queue connection.
+ */
+ 'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'),
+
+ /*
+ * This queue will be used to generate derived and responsive images.
+ * Leave empty to use the default queue.
+ */
+ 'queue_name' => env('MEDIA_QUEUE', ''),
+
+ /*
+ * By default all conversions will be performed on a queue.
+ */
+ 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true),
+
+ /*
+ * Should database transactions be run after database commits?
+ */
+ 'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true),
+
+ /*
+ * The fully qualified class name of the media model.
+ */
+ 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
+
+ /*
+ * The fully qualified class name of the media observer.
+ */
+ 'media_observer' => Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver::class,
+
+ /*
+ * When enabled, media collections will be serialised using the default
+ * laravel model serialization behaviour.
+ *
+ * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro)
+ */
+ 'use_default_collection_serialization' => false,
+
+ /*
+ * The fully qualified class name of the model used for temporary uploads.
+ *
+ * This model is only used in Media Library Pro (https://medialibrary.pro)
+ */
+ 'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class,
+
+ /*
+ * When enabled, Media Library Pro will only process temporary uploads that were uploaded
+ * in the same session. You can opt to disable this for stateless usage of
+ * the pro components.
+ */
+ 'enable_temporary_uploads_session_affinity' => true,
+
+ /*
+ * When enabled, Media Library pro will generate thumbnails for uploaded file.
+ */
+ 'generate_thumbnails_for_temporary_uploads' => true,
+
+ /*
+ * This is the class that is responsible for naming generated files.
+ */
+ 'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
+
+ /*
+ * The class that contains the strategy for determining a media file's path.
+ */
+ 'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
+
+ /*
+ * The class that contains the strategy for determining how to remove files.
+ */
+ 'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class,
+
+ /*
+ * Here you can specify which path generator should be used for the given class.
+ */
+ 'custom_path_generators' => [
+ // Model::class => PathGenerator::class
+ // or
+ // 'model_morph_alias' => PathGenerator::class
+ ],
+
+ /*
+ * When urls to files get generated, this class will be called. Use the default
+ * if your files are stored locally above the site root or on s3.
+ */
+ 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
+
+ /*
+ * Moves media on updating to keep path consistent. Enable it only with a custom
+ * PathGenerator that uses, for example, the media UUID.
+ */
+ 'moves_media_on_update' => false,
+
+ /*
+ * Whether to activate versioning when urls to files get generated.
+ * When activated, this attaches a ?v=xx query string to the URL.
+ */
+ 'version_urls' => false,
+
+ /*
+ * The media library will try to optimize all converted images by removing
+ * metadata and applying a little bit of compression. These are
+ * the optimizers that will be used by default.
+ */
+ 'image_optimizers' => [
+ Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
+ '-m85', // set maximum quality to 85%
+ '--force', // ensure that progressive generation is always done also if a little bigger
+ '--strip-all', // this strips out all text information such as comments and EXIF data
+ '--all-progressive', // this will make sure the resulting image is a progressive one
+ ],
+ Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
+ '--force', // required parameter for this package
+ ],
+ Spatie\ImageOptimizer\Optimizers\Optipng::class => [
+ '-i0', // this will result in a non-interlaced, progressive scanned image
+ '-o2', // this set the optimization level to two (multiple IDAT compression trials)
+ '-quiet', // required parameter for this package
+ ],
+ Spatie\ImageOptimizer\Optimizers\Svgo::class => [
+ '--disable=cleanupIDs', // disabling because it is known to cause troubles
+ ],
+ Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
+ '-b', // required parameter for this package
+ '-O3', // this produces the slowest but best results
+ ],
+ Spatie\ImageOptimizer\Optimizers\Cwebp::class => [
+ '-m 6', // for the slowest compression method in order to get the best compression.
+ '-pass 10', // for maximizing the amount of analysis pass.
+ '-mt', // multithreading for some speed improvements.
+ '-q 90', // quality factor that brings the least noticeable changes.
+ ],
+ Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
+ '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).
+ '-j all', // number of jobs (worker threads, "all" uses all available cores).
+ '--min 0', // min quantizer for color (0-63).
+ '--max 63', // max quantizer for color (0-63).
+ '--minalpha 0', // min quantizer for alpha (0-63).
+ '--maxalpha 63', // max quantizer for alpha (0-63).
+ '-a end-usage=q', // rate control mode set to Constant Quality mode.
+ '-a tune=ssim', // SSIM as tune the encoder for distortion metric.
+ ],
+ ],
+
+ /*
+ * These generators will be used to create an image of media files.
+ */
+ 'image_generators' => [
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class,
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
+ Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
+ ],
+
+ /*
+ * The path where to store temporary files while performing image conversions.
+ * If set to null, storage_path('media-library/temp') will be used.
+ */
+ 'temporary_directory_path' => null,
+
+ /*
+ * The engine that should perform the image conversions.
+ * Should be either `gd`, `imagick` or `vips`.
+ */
+ 'image_driver' => env('IMAGE_DRIVER', 'gd'),
+
+ /*
+ * FFMPEG & FFProbe binaries paths, only used if you try to generate video
+ * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
+ * dependency.
+ */
+ 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
+ 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
+
+ /*
+ * The timeout (in seconds) that will be used when generating video
+ * thumbnails via FFMPEG.
+ */
+ 'ffmpeg_timeout' => env('FFMPEG_TIMEOUT', 900),
+
+ /*
+ * The number of threads that FFMPEG should use. 0 means that FFMPEG
+ * may decide itself.
+ */
+ 'ffmpeg_threads' => env('FFMPEG_THREADS', 0),
+
+ /*
+ * Here you can override the class names of the jobs used by this package. Make sure
+ * your custom jobs extend the ones provided by the package.
+ */
+ 'jobs' => [
+ 'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
+ 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
+ ],
+
+ /*
+ * When using the addMediaFromUrl method you may want to replace the default downloader.
+ * This is particularly useful when the url of the image is behind a firewall and
+ * need to add additional flags, possibly using curl.
+ */
+ 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
+
+ /*
+ * When using the addMediaFromUrl method the SSL is verified by default.
+ * This is option disables SSL verification when downloading remote media.
+ * Please note that this is a security risk and should only be false in a local environment.
+ */
+ 'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true),
+
+ /*
+ * The default lifetime in minutes for temporary urls.
+ * This is used when you call the `getLastTemporaryUrl` or `getLastTemporaryUrl` method on a media item.
+ */
+ 'temporary_url_default_lifetime' => env('MEDIA_TEMPORARY_URL_DEFAULT_LIFETIME', 5),
+
+ 'remote' => [
+ /*
+ * Any extra headers that should be included when uploading media to
+ * a remote disk. Even though supported headers may vary between
+ * different drivers, a sensible default has been provided.
+ *
+ * Supported by S3: CacheControl, Expires, StorageClass,
+ * ServerSideEncryption, Metadata, ACL, ContentEncoding
+ */
+ 'extra_headers' => [
+ 'CacheControl' => 'max-age=604800',
+ ],
+ ],
+
+ 'responsive_images' => [
+ /*
+ * This class is responsible for calculating the target widths of the responsive
+ * images. By default we optimize for filesize and create variations that each are 30%
+ * smaller than the previous one. More info in the documentation.
+ *
+ * https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images
+ */
+ 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
+
+ /*
+ * By default rendering media to a responsive image will add some javascript and a tiny placeholder.
+ * This ensures that the browser can already determine the correct layout.
+ * When disabled, no tiny placeholder is generated.
+ */
+ 'use_tiny_placeholders' => true,
+
+ /*
+ * This class will generate the tiny placeholder used for progressive image loading. By default
+ * the media library will use a tiny blurred jpg image.
+ */
+ 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
+ ],
+
+ /*
+ * When enabling this option, a route will be registered that will enable
+ * the Media Library Pro Vue and React components to move uploaded files
+ * in a S3 bucket to their right place.
+ */
+ 'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false),
+
+ /*
+ * When converting Media instances to response the media library will add
+ * a `loading` attribute to the `img` tag. Here you can set the default
+ * value of that attribute.
+ *
+ * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction.
+ *
+ * More info: https://css-tricks.com/native-lazy-loading/
+ */
+ 'default_loading_attribute_value' => null,
+
+ /*
+ * You can specify a prefix for that is used for storing all media.
+ * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory.
+ */
+ 'prefix' => env('MEDIA_PREFIX', ''),
+
+ /*
+ * When forcing lazy loading, media will be loaded even if you don't eager load media and you have
+ * disabled lazy loading globally in the service provider.
+ */
+ 'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true),
+];
diff --git a/config/permission.php b/config/permission.php
new file mode 100644
index 0000000..082ca30
--- /dev/null
+++ b/config/permission.php
@@ -0,0 +1,202 @@
+ [
+
+ /*
+ * When using the "HasPermissions" trait from this package, we need to know which
+ * Eloquent model should be used to retrieve your permissions. Of course, it
+ * is often just the "Permission" model but you may use whatever you like.
+ *
+ * The model you want to use as a Permission model needs to implement the
+ * `Spatie\Permission\Contracts\Permission` contract.
+ */
+
+ 'permission' => Spatie\Permission\Models\Permission::class,
+
+ /*
+ * When using the "HasRoles" trait from this package, we need to know which
+ * Eloquent model should be used to retrieve your roles. Of course, it
+ * is often just the "Role" model but you may use whatever you like.
+ *
+ * The model you want to use as a Role model needs to implement the
+ * `Spatie\Permission\Contracts\Role` contract.
+ */
+
+ 'role' => Spatie\Permission\Models\Role::class,
+
+ ],
+
+ 'table_names' => [
+
+ /*
+ * When using the "HasRoles" trait from this package, we need to know which
+ * table should be used to retrieve your roles. We have chosen a basic
+ * default value but you may easily change it to any table you like.
+ */
+
+ 'roles' => 'roles',
+
+ /*
+ * When using the "HasPermissions" trait from this package, we need to know which
+ * table should be used to retrieve your permissions. We have chosen a basic
+ * default value but you may easily change it to any table you like.
+ */
+
+ 'permissions' => 'permissions',
+
+ /*
+ * When using the "HasPermissions" trait from this package, we need to know which
+ * table should be used to retrieve your models permissions. We have chosen a
+ * basic default value but you may easily change it to any table you like.
+ */
+
+ 'model_has_permissions' => 'model_has_permissions',
+
+ /*
+ * When using the "HasRoles" trait from this package, we need to know which
+ * table should be used to retrieve your models roles. We have chosen a
+ * basic default value but you may easily change it to any table you like.
+ */
+
+ 'model_has_roles' => 'model_has_roles',
+
+ /*
+ * When using the "HasRoles" trait from this package, we need to know which
+ * table should be used to retrieve your roles permissions. We have chosen a
+ * basic default value but you may easily change it to any table you like.
+ */
+
+ 'role_has_permissions' => 'role_has_permissions',
+ ],
+
+ 'column_names' => [
+ /*
+ * Change this if you want to name the related pivots other than defaults
+ */
+ 'role_pivot_key' => null, // default 'role_id',
+ 'permission_pivot_key' => null, // default 'permission_id',
+
+ /*
+ * Change this if you want to name the related model primary key other than
+ * `model_id`.
+ *
+ * For example, this would be nice if your primary keys are all UUIDs. In
+ * that case, name this `model_uuid`.
+ */
+
+ 'model_morph_key' => 'model_id',
+
+ /*
+ * Change this if you want to use the teams feature and your related model's
+ * foreign key is other than `team_id`.
+ */
+
+ 'team_foreign_key' => 'team_id',
+ ],
+
+ /*
+ * When set to true, the method for checking permissions will be registered on the gate.
+ * Set this to false if you want to implement custom logic for checking permissions.
+ */
+
+ 'register_permission_check_method' => true,
+
+ /*
+ * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
+ * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
+ * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
+ */
+ 'register_octane_reset_listener' => false,
+
+ /*
+ * Events will fire when a role or permission is assigned/unassigned:
+ * \Spatie\Permission\Events\RoleAttachedEvent
+ * \Spatie\Permission\Events\RoleDetachedEvent
+ * \Spatie\Permission\Events\PermissionAttachedEvent
+ * \Spatie\Permission\Events\PermissionDetachedEvent
+ *
+ * To enable, set to true, and then create listeners to watch these events.
+ */
+ 'events_enabled' => false,
+
+ /*
+ * Teams Feature.
+ * When set to true the package implements teams using the 'team_foreign_key'.
+ * If you want the migrations to register the 'team_foreign_key', you must
+ * set this to true before doing the migration.
+ * If you already did the migration then you must make a new migration to also
+ * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
+ * (view the latest version of this package's migration file)
+ */
+
+ 'teams' => false,
+
+ /*
+ * The class to use to resolve the permissions team id
+ */
+ 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
+
+ /*
+ * Passport Client Credentials Grant
+ * When set to true the package will use Passports Client to check permissions
+ */
+
+ 'use_passport_client_credentials' => false,
+
+ /*
+ * When set to true, the required permission names are added to exception messages.
+ * This could be considered an information leak in some contexts, so the default
+ * setting is false here for optimum safety.
+ */
+
+ 'display_permission_in_exception' => false,
+
+ /*
+ * When set to true, the required role names are added to exception messages.
+ * This could be considered an information leak in some contexts, so the default
+ * setting is false here for optimum safety.
+ */
+
+ 'display_role_in_exception' => false,
+
+ /*
+ * By default wildcard permission lookups are disabled.
+ * See documentation to understand supported syntax.
+ */
+
+ 'enable_wildcard_permission' => false,
+
+ /*
+ * The class to use for interpreting wildcard permissions.
+ * If you need to modify delimiters, override the class and specify its name here.
+ */
+ // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
+
+ /* Cache-specific settings */
+
+ 'cache' => [
+
+ /*
+ * By default all permissions are cached for 24 hours to speed up performance.
+ * When permissions or roles are updated the cache is flushed automatically.
+ */
+
+ 'expiration_time' => \DateInterval::createFromDateString('24 hours'),
+
+ /*
+ * The cache key used to store all permissions.
+ */
+
+ 'key' => 'spatie.permission.cache',
+
+ /*
+ * You may optionally indicate a specific cache driver to use for permission and
+ * role caching using any of the `store` drivers listed in the cache.php config
+ * file. Using 'default' here means to use the `default` set in cache.php.
+ */
+
+ 'store' => 'default',
+ ],
+];
diff --git a/config/responsecache.php b/config/responsecache.php
new file mode 100644
index 0000000..9d691e9
--- /dev/null
+++ b/config/responsecache.php
@@ -0,0 +1,120 @@
+ env('RESPONSE_CACHE_ENABLED', false),
+
+ 'cache' => [
+ /*
+ * Here you may define the cache store that should be used
+ * to store requests. This can be the name of any store
+ * that is configured in your app's cache.php config
+ */
+ 'store' => env('RESPONSE_CACHE_DRIVER', 'file'),
+
+ /*
+ * The default number of seconds responses will be cached
+ * when using the default CacheProfile settings.
+ */
+ 'lifetime_in_seconds' => (int) env('RESPONSE_CACHE_LIFETIME', 300),
+
+ /*
+ * If your cache driver supports tags, you may specify a tag
+ * name here. All responses will be tagged. When clearing
+ * the responsecache only items with that tag flushed.
+ *
+ * You may use a string or an array here.
+ */
+ 'tag' => env('RESPONSE_CACHE_TAG', ''),
+ ],
+
+ 'bypass' => [
+ /*
+ * The header name that will force a bypass of the cache.
+ * This is useful when you want to see the performance
+ * of your application without the caching enabled.
+ */
+ 'header_name' => env('CACHE_BYPASS_HEADER_NAME'),
+
+ /*
+ * The header value that will force a cache bypass.
+ */
+ 'header_value' => env('CACHE_BYPASS_HEADER_VALUE'),
+ ],
+
+ 'debug' => [
+ /*
+ * Determines if debug headers are added to cached
+ * responses. This can be handy for debugging how
+ * response caching is performing in your app.
+ */
+ 'enabled' => env('APP_DEBUG', false),
+
+ /*
+ * The name of the http header containing the
+ * point at which the response was cached.
+ */
+ 'cache_time_header_name' => 'X-Cache-Time',
+
+ /*
+ * The name of the header for the cache status that
+ * indicates whether a response was HIT or MISS.
+ */
+ 'cache_status_header_name' => 'X-Cache-Status',
+
+ /*
+ * The header name for the cache age in seconds.
+ */
+ 'cache_age_header_name' => 'X-Cache-Age',
+
+ /*
+ * The header name used for the response cache key.
+ * This is only added when app.debug is enabled.
+ */
+ 'cache_key_header_name' => 'X-Cache-Key',
+ ],
+
+ /*
+ * These query parameters will be ignored when generating
+ * the cache key. This is useful for ignoring tracking
+ * parameters like UTM tags, gclid and also fbclid.
+ */
+ 'ignored_query_parameters' => [
+ 'utm_source',
+ 'utm_medium',
+ 'utm_campaign',
+ 'utm_term',
+ 'utm_content',
+ 'gclid',
+ 'fbclid',
+ ],
+
+ /*
+ * The given class determines if a request should be cached.
+ * By default all successful GET-requests will be cached.
+ * You can provide your own by using the CacheProfile.
+ */
+ 'cache_profile' => Spatie\ResponseCache\CacheProfiles\CacheAllSuccessfulGetRequests::class,
+
+ /*
+ * This class is responsible for generating a hash for
+ * a request. Used for looking up cached responses.
+ */
+ 'hasher' => \Spatie\ResponseCache\Hasher\DefaultHasher::class,
+
+ /*
+ * This class is responsible for serializing responses.
+ */
+ 'serializer' => \Spatie\ResponseCache\Serializers\JsonSerializer::class,
+
+ /*
+ * Here you may define the replacers that will replace
+ * dynamic content from the response. Each replacer
+ * must always implement the Replacer interface.
+ */
+ 'replacers' => [
+ \Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class,
+ ],
+];
diff --git a/config/seotools.php b/config/seotools.php
new file mode 100644
index 0000000..a109d7c
--- /dev/null
+++ b/config/seotools.php
@@ -0,0 +1,69 @@
+ env('SEO_TOOLS_INERTIA', false),
+ 'meta' => [
+ /*
+ * The default configurations to be used by the meta generator.
+ */
+ 'defaults' => [
+ 'title' => "It's Over 9000!", // set false to total remove
+ 'titleBefore' => false, // Put defaults.title before page title, like 'It's Over 9000! - Dashboard'
+ 'description' => 'For those who helped create the Genki Dama', // set false to total remove
+ 'separator' => ' - ',
+ 'keywords' => [],
+ 'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
+ 'robots' => false, // Set to 'all', 'none' or any combination of index/noindex and follow/nofollow
+ ],
+ /*
+ * Webmaster tags are always added.
+ */
+ 'webmaster_tags' => [
+ 'google' => null,
+ 'bing' => null,
+ 'alexa' => null,
+ 'pinterest' => null,
+ 'yandex' => null,
+ 'norton' => null,
+ ],
+
+ 'add_notranslate_class' => false,
+ ],
+ 'opengraph' => [
+ /*
+ * The default configurations to be used by the opengraph generator.
+ */
+ 'defaults' => [
+ 'title' => 'Over 9000 Thousand!', // set false to total remove
+ 'description' => 'For those who helped create the Genki Dama', // set false to total remove
+ 'url' => false, // Set null for using Url::current(), set false to total remove
+ 'type' => false,
+ 'site_name' => false,
+ 'images' => [],
+ ],
+ ],
+ 'twitter' => [
+ /*
+ * The default values to be used by the twitter cards generator.
+ */
+ 'defaults' => [
+ //'card' => 'summary',
+ //'site' => '@LuizVinicius73',
+ ],
+ ],
+ 'json-ld' => [
+ /*
+ * The default configurations to be used by the json-ld generator.
+ */
+ 'defaults' => [
+ 'title' => 'Over 9000 Thousand!', // set false to total remove
+ 'description' => 'For those who helped create the Genki Dama', // set false to total remove
+ 'url' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
+ 'type' => 'WebPage',
+ 'images' => [],
+ ],
+ ],
+];
diff --git a/config/sitemap.php b/config/sitemap.php
new file mode 100644
index 0000000..69be0f3
--- /dev/null
+++ b/config/sitemap.php
@@ -0,0 +1,57 @@
+ [
+
+ /*
+ * Whether or not cookies are used in a request.
+ */
+ RequestOptions::COOKIES => true,
+
+ /*
+ * The number of seconds to wait while trying to connect to a server.
+ * Use 0 to wait indefinitely.
+ */
+ RequestOptions::CONNECT_TIMEOUT => 10,
+
+ /*
+ * The timeout of the request in seconds. Use 0 to wait indefinitely.
+ */
+ RequestOptions::TIMEOUT => 10,
+
+ /*
+ * Describes the redirect behavior of a request.
+ */
+ RequestOptions::ALLOW_REDIRECTS => false,
+ ],
+
+ /*
+ * The sitemap generator can execute JavaScript on each page so it will
+ * discover links that are generated by your JS scripts. This feature
+ * is powered by headless Chrome.
+ */
+ 'execute_javascript' => false,
+
+ /*
+ * The package will make an educated guess as to where Google Chrome is installed.
+ * You can also manually pass its location here.
+ */
+ 'chrome_binary_path' => null,
+
+ /*
+ * The sitemap generator uses a CrawlProfile implementation to determine
+ * which urls should be crawled for the sitemap.
+ */
+ 'crawl_profile' => Profile::class,
+
+];
diff --git a/database/migrations/2026_03_10_020226_create_tags_table.php b/database/migrations/2026_03_10_020226_create_tags_table.php
new file mode 100644
index 0000000..bdb76e2
--- /dev/null
+++ b/database/migrations/2026_03_10_020226_create_tags_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->string('name');
+ $table->string('slug')->unique();
+ $table->timestamps();
+
+ $table->unique('name');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('tags');
+ }
+};
diff --git a/database/migrations/2026_03_10_020230_create_categories_table.php b/database/migrations/2026_03_10_020230_create_categories_table.php
new file mode 100644
index 0000000..f47762a
--- /dev/null
+++ b/database/migrations/2026_03_10_020230_create_categories_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->string('name');
+ $table->string('slug')->unique();
+ $table->text('description')->nullable();
+ $table->enum('status', ['active', 'inactive'])->default('inactive');
+ $table->timestamps();
+
+ $table->unique('name');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('categories');
+ }
+};
diff --git a/database/migrations/2026_03_10_020231_create_articles_table.php b/database/migrations/2026_03_10_020231_create_articles_table.php
new file mode 100644
index 0000000..73ba7fe
--- /dev/null
+++ b/database/migrations/2026_03_10_020231_create_articles_table.php
@@ -0,0 +1,45 @@
+id();
+ $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
+ $table->foreignId('author_id')->nullable()->constrained('users')->nullOnDelete();
+ $table->string('title');
+ $table->string('slug')->unique();
+ $table->string('excerpt', 500)->nullable();
+ $table->longText('content');
+ $table->enum('status', ['draft', 'published'])->default('draft');
+ $table->timestamp('published_at')->nullable();
+ $table->timestamps();
+
+ $table->index(['status', 'published_at']);
+ });
+
+ Schema::create('article_tag', function (Blueprint $table) {
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
+
+ $table->primary(['article_id', 'tag_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('article_tag');
+ Schema::dropIfExists('articles');
+ }
+};
diff --git a/database/migrations/2026_03_10_020236_create_article_versions_table.php b/database/migrations/2026_03_10_020236_create_article_versions_table.php
new file mode 100644
index 0000000..12570d5
--- /dev/null
+++ b/database/migrations/2026_03_10_020236_create_article_versions_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->unsignedInteger('version_number');
+ $table->longText('content');
+ $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->timestamps();
+
+ $table->unique(['article_id', 'version_number']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('article_versions');
+ }
+};
diff --git a/database/migrations/2026_03_10_020342_create_comments_table.php b/database/migrations/2026_03_10_020342_create_comments_table.php
new file mode 100644
index 0000000..51cdf31
--- /dev/null
+++ b/database/migrations/2026_03_10_020342_create_comments_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
+ $table->string('author_name')->nullable();
+ $table->string('author_email')->nullable();
+ $table->text('content');
+ $table->boolean('is_approved')->default(false);
+ $table->timestamps();
+
+ $table->index(['article_id', 'is_approved']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('comments');
+ }
+};
diff --git a/database/migrations/2026_03_10_020344_create_analytics_table.php b/database/migrations/2026_03_10_020344_create_analytics_table.php
new file mode 100644
index 0000000..f97fab7
--- /dev/null
+++ b/database/migrations/2026_03_10_020344_create_analytics_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->unsignedBigInteger('views')->default(0);
+ $table->unsignedBigInteger('likes')->default(0);
+ $table->unsignedBigInteger('shares')->default(0);
+ $table->date('tracked_date');
+ $table->timestamps();
+
+ $table->unique(['article_id', 'tracked_date']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('analytics');
+ }
+};
diff --git a/database/migrations/2026_03_10_020346_create_trendings_table.php b/database/migrations/2026_03_10_020346_create_trendings_table.php
new file mode 100644
index 0000000..5e6c44f
--- /dev/null
+++ b/database/migrations/2026_03_10_020346_create_trendings_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->unsignedInteger('score')->default(0);
+ $table->unsignedInteger('rank')->nullable();
+ $table->timestamp('calculated_at');
+ $table->timestamps();
+
+ $table->index(['score', 'calculated_at']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('trendings');
+ }
+};
diff --git a/database/migrations/2026_03_10_020432_create_ads_table.php b/database/migrations/2026_03_10_020432_create_ads_table.php
new file mode 100644
index 0000000..970f8eb
--- /dev/null
+++ b/database/migrations/2026_03_10_020432_create_ads_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->string('name');
+ $table->string('placement');
+ $table->string('image_url')->nullable();
+ $table->string('target_url');
+ $table->boolean('is_active')->default(true);
+ $table->unsignedInteger('priority')->default(0);
+ $table->timestamp('starts_at')->nullable();
+ $table->timestamp('ends_at')->nullable();
+ $table->timestamps();
+
+ $table->index(['placement', 'is_active']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('ads');
+ }
+};
diff --git a/database/migrations/2026_03_10_022442_create_activity_log_table.php b/database/migrations/2026_03_10_022442_create_activity_log_table.php
new file mode 100644
index 0000000..7c05bc8
--- /dev/null
+++ b/database/migrations/2026_03_10_022442_create_activity_log_table.php
@@ -0,0 +1,27 @@
+create(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->string('log_name')->nullable();
+ $table->text('description');
+ $table->nullableMorphs('subject', 'subject');
+ $table->nullableMorphs('causer', 'causer');
+ $table->json('properties')->nullable();
+ $table->timestamps();
+ $table->index('log_name');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
+ }
+}
diff --git a/database/migrations/2026_03_10_022443_add_event_column_to_activity_log_table.php b/database/migrations/2026_03_10_022443_add_event_column_to_activity_log_table.php
new file mode 100644
index 0000000..7b797fd
--- /dev/null
+++ b/database/migrations/2026_03_10_022443_add_event_column_to_activity_log_table.php
@@ -0,0 +1,22 @@
+table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->string('event')->nullable()->after('subject_type');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->dropColumn('event');
+ });
+ }
+}
diff --git a/database/migrations/2026_03_10_022444_add_batch_uuid_column_to_activity_log_table.php b/database/migrations/2026_03_10_022444_add_batch_uuid_column_to_activity_log_table.php
new file mode 100644
index 0000000..8f7db66
--- /dev/null
+++ b/database/migrations/2026_03_10_022444_add_batch_uuid_column_to_activity_log_table.php
@@ -0,0 +1,22 @@
+table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->uuid('batch_uuid')->nullable()->after('properties');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->dropColumn('batch_uuid');
+ });
+ }
+}
diff --git a/database/migrations/2026_03_10_022729_create_media_table.php b/database/migrations/2026_03_10_022729_create_media_table.php
new file mode 100644
index 0000000..47a4be9
--- /dev/null
+++ b/database/migrations/2026_03_10_022729_create_media_table.php
@@ -0,0 +1,32 @@
+id();
+
+ $table->morphs('model');
+ $table->uuid()->nullable()->unique();
+ $table->string('collection_name');
+ $table->string('name');
+ $table->string('file_name');
+ $table->string('mime_type')->nullable();
+ $table->string('disk');
+ $table->string('conversions_disk')->nullable();
+ $table->unsignedBigInteger('size');
+ $table->json('manipulations');
+ $table->json('custom_properties');
+ $table->json('generated_conversions');
+ $table->json('responsive_images');
+ $table->unsignedInteger('order_column')->nullable()->index();
+
+ $table->nullableTimestamps();
+ });
+ }
+};
diff --git a/database/migrations/2026_03_10_030000_create_article_seos_table.php b/database/migrations/2026_03_10_030000_create_article_seos_table.php
new file mode 100644
index 0000000..d76d495
--- /dev/null
+++ b/database/migrations/2026_03_10_030000_create_article_seos_table.php
@@ -0,0 +1,32 @@
+id();
+ $table->foreignId('article_id')->constrained()->cascadeOnDelete();
+ $table->string('title')->nullable();
+ $table->string('description', 500)->nullable();
+ $table->string('keywords')->nullable();
+ $table->string('og_image')->nullable();
+ $table->unsignedTinyInteger('score')->default(0);
+ $table->json('score_breakdown')->nullable();
+ $table->json('warnings')->nullable();
+ $table->timestamp('last_analyzed_at')->nullable();
+ $table->timestamps();
+
+ $table->unique('article_id');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('article_seos');
+ }
+};
diff --git a/database/migrations/2026_03_10_035016_create_permission_tables.php b/database/migrations/2026_03_10_035016_create_permission_tables.php
new file mode 100644
index 0000000..8986275
--- /dev/null
+++ b/database/migrations/2026_03_10_035016_create_permission_tables.php
@@ -0,0 +1,137 @@
+id(); // permission id
+ $table->string('name');
+ $table->string('guard_name');
+ $table->timestamps();
+
+ $table->unique(['name', 'guard_name']);
+ });
+
+ /**
+ * See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
+ */
+ Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
+ $table->id(); // role id
+ if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
+ $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
+ $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
+ }
+ $table->string('name');
+ $table->string('guard_name');
+ $table->timestamps();
+ if ($teams || config('permission.testing')) {
+ $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
+ } else {
+ $table->unique(['name', 'guard_name']);
+ }
+ });
+
+ Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
+ $table->unsignedBigInteger($pivotPermission);
+
+ $table->string('model_type');
+ $table->unsignedBigInteger($columnNames['model_morph_key']);
+ $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
+
+ $table->foreign($pivotPermission)
+ ->references('id') // permission id
+ ->on($tableNames['permissions'])
+ ->cascadeOnDelete();
+ if ($teams) {
+ $table->unsignedBigInteger($columnNames['team_foreign_key']);
+ $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
+
+ $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
+ 'model_has_permissions_permission_model_type_primary');
+ } else {
+ $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
+ 'model_has_permissions_permission_model_type_primary');
+ }
+ });
+
+ Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
+ $table->unsignedBigInteger($pivotRole);
+
+ $table->string('model_type');
+ $table->unsignedBigInteger($columnNames['model_morph_key']);
+ $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
+
+ $table->foreign($pivotRole)
+ ->references('id') // role id
+ ->on($tableNames['roles'])
+ ->cascadeOnDelete();
+ if ($teams) {
+ $table->unsignedBigInteger($columnNames['team_foreign_key']);
+ $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
+
+ $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
+ 'model_has_roles_role_model_type_primary');
+ } else {
+ $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
+ 'model_has_roles_role_model_type_primary');
+ }
+ });
+
+ Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
+ $table->unsignedBigInteger($pivotPermission);
+ $table->unsignedBigInteger($pivotRole);
+
+ $table->foreign($pivotPermission)
+ ->references('id') // permission id
+ ->on($tableNames['permissions'])
+ ->cascadeOnDelete();
+
+ $table->foreign($pivotRole)
+ ->references('id') // role id
+ ->on($tableNames['roles'])
+ ->cascadeOnDelete();
+
+ $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
+ });
+
+ app('cache')
+ ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
+ ->forget(config('permission.cache.key'));
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ $tableNames = config('permission.table_names');
+
+ throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
+
+ Schema::dropIfExists($tableNames['role_has_permissions']);
+ Schema::dropIfExists($tableNames['model_has_roles']);
+ Schema::dropIfExists($tableNames['model_has_permissions']);
+ Schema::dropIfExists($tableNames['roles']);
+ Schema::dropIfExists($tableNames['permissions']);
+ }
+};
diff --git a/database/migrations/2026_03_10_210000_create_contact_submissions_table.php b/database/migrations/2026_03_10_210000_create_contact_submissions_table.php
new file mode 100644
index 0000000..5c0125d
--- /dev/null
+++ b/database/migrations/2026_03_10_210000_create_contact_submissions_table.php
@@ -0,0 +1,40 @@
+id();
+ $table->string('name', 120);
+ $table->string('email', 190);
+ $table->string('phone', 30);
+ $table->string('subject', 180);
+ $table->text('message');
+ $table->enum('status', ['pending', 'processed'])->default('pending');
+ $table->ipAddress('ip_address')->nullable();
+ $table->date('submitted_date')->nullable();
+ $table->timestamps();
+
+ $table->unique(['submitted_date', 'ip_address'], 'contact_submissions_date_ip_unique');
+ $table->unique(['submitted_date', 'email'], 'contact_submissions_date_email_unique');
+ $table->unique(['submitted_date', 'phone'], 'contact_submissions_date_phone_unique');
+ $table->index('created_at');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('contact_submissions');
+ }
+};
diff --git a/database/seeders/ArticleSeeder.php b/database/seeders/ArticleSeeder.php
new file mode 100644
index 0000000..8a9257c
--- /dev/null
+++ b/database/seeders/ArticleSeeder.php
@@ -0,0 +1,89 @@
+where('email', 'test@example.com')->value('id');
+
+ $categories = collect([
+ ['name' => 'Technology', 'description' => 'Tech news and tutorials'],
+ ['name' => 'Business', 'description' => 'Business and startup stories'],
+ ['name' => 'Marketing', 'description' => 'Marketing insights and playbooks'],
+ ['name' => 'Product', 'description' => 'Product management and growth'],
+ ])->map(function (array $item): Category {
+ return Category::query()->firstOrCreate(
+ ['slug' => Str::slug($item['name'])],
+ [
+ 'name' => $item['name'],
+ 'description' => $item['description'],
+ 'status' => CategoryStatus::ACTIVE,
+ ]
+ );
+ });
+
+ $tags = collect([
+ 'laravel',
+ 'php',
+ 'seo',
+ 'redis',
+ 'meilisearch',
+ 'architecture',
+ 'performance',
+ 'devops',
+ ])->map(function (string $name): Tag {
+ return Tag::query()->firstOrCreate(
+ ['slug' => Str::slug($name)],
+ ['name' => Str::title($name)]
+ );
+ });
+
+ $baseContent = `Theo Daum, tác phẩm lấy bối cảnh sau khi vương triều Cao Câu Ly sụp đổ, xoay quanh Chilseong (Park Bo Gum), một võ sĩ mất ký ức bị đưa vào đấu trường nô lệ để giành lấy thanh kiếm huyền thoại. Trong phim, Trấn Thành vào vai In Gwi (Nhân Quý) - tổng quản phủ An Đông đô hộ của nhà Đường, nhân vật có ảnh hưởng lớn đến cục diện chính trị ở miền Bắc. Trấn Thành trong buổi đọc kịch bản phim "Kal: Thanh kiếm của Godumakhan". Ảnh: Red Ice Entertainment/Solar Partners Tại buổi họp của đoàn phim đăng tải ngày 9/3, nghệ sĩ xuất hiện với trang phục tối màu, tập trung theo dõi kịch bản và trao đổi với êkíp. Các diễn viên còn thực hiện một số động tác chiến đấu để thống nhất nhịp độ hành động.Theo Star News, vai diễn của Trấn Thành ban đầu được giao cho tài tử Cha Seung Won. Tuy nhiên, do lịch quay dự kiến tháng 8/2025 bị lùi sang năm nay, diễn viên đã rút khỏi dự án.Ngoài Trấn Thành và Park Bo Gum, tác phẩm có sự tham gia của Joo Won, Jung Jae Young và Lee Sun Bin. Joo Won hóa thân Gye Pil Hyeok, chiến binh đối đầu Chilseong, có kỹ năng sử dụng song kiếm. Jung Jae Young đảm nhận nhân vật Heuksugang - thủ lĩnh lực lượng phục hưng Cao Câu Ly, giữ vai trò trung tâm trong bối cảnh thời cuộc hỗn loạn. Lee Sun Bin đóng Maya, thành viên của lực lượng phục hưng.Dự án do đạo diễn Kim Han Min - đứng sau các tác phẩm ăn khách như Đại thủy chiến, Thủy chiến đảo Hansan: Rồng trỗi dậy và Đại hải chiến Noryang - Biển chết - thực hiện. Tác phẩm dự kiến ra mắt vào năm 2027, hướng tới phát hành tại nhiều thị trường quốc tế, trong đó có Nhật Bản và Việt Nam. Trailer 'Đại hải chiến Noryang - Biển chết' Trailer "Đại hải chiến Noryang - Biển chết" (2023), do Kim Han Min chỉ đạo. Video: Lotte Entertainment Vietnam Trấn Thành tên đầy đủ Huỳnh Trấn Thành, 39 tuổi, là diễn viên, người dẫn chương trình, nhà làm phim. Năm 2021, nghệ sĩ gây tiếng vang khi làm phim điện ảnh đầu tay Bố già, đoạt nhiều giải thưởng trong nước như Bông Sen Vàng, Cánh Diều Vàng. Năm 2023, anh phát hành Nhà bà Nữ, tác phẩm đầu tiên tự đạo diễn, đạt doanh thu cao nhất mọi thời tại phòng vé trong nước khi đó.Phim Mai của anh - ra mắt Tết Giáp Thìn 2024, dán nhãn 18+ (không dành cho khán giả dưới 18 tuổi) - từng là tác phẩm Việt ăn khách nhất phòng vé với 520 tỷ đồng, cho đến khi bị Mưa đỏ (đạo diễn Đặng Thái Huyền) phá kỷ lục. Dịp Tết Ất Tỵ 2025, Bộ tứ báo thủ thu về hơn 300 tỷ đồng, là một trong những phim Việt doanh thu cao nhất năm.Về đời tư, anh công khai yêu đương ca sĩ Hari Won vào tháng 2/2016. Cả hai kết hôn tháng 12/2016. Vợ chồng nghệ sĩ luôn sát cánh trong công việc.Cát Tiên (theo Nate, NC Press)`;
+ $sharedContent = substr(str_repeat($baseContent, 12), 0, 1000);
+ $descriptionContent = substr(str_repeat($baseContent, 12), 0, 200);
+
+ for ($i = 1; $i <= 20; $i++) {
+ $title = sprintf('Trấn Thành đóng phim điện ảnh Hàn %02d', $i);
+ $slug = Str::slug($title);
+
+ /** @var Category $category */
+ $category = $categories->random();
+
+ $article = Article::query()->updateOrCreate(
+ ['slug' => $slug],
+ [
+ 'category_id' => $category->id,
+ 'author_id' => $defaultAuthorId,
+ 'title' => $title,
+ 'slug' => $slug,
+ 'excerpt' => $descriptionContent,
+ 'content' => $sharedContent,
+ 'status' => ArticleStatus::PUBLISHED,
+ 'published_at' => now()->subDays(21 - $i),
+ ]
+ );
+
+ $article->tags()->sync(
+ $tags->random(rand(2, 4))->pluck('id')->all()
+ );
+
+ $article->seo()->updateOrCreate([], [
+ 'title' => $title,
+ 'description' => $descriptionContent,
+ 'keywords' => 'laravel,content,seo,cache,permission',
+ 'og_image' => null,
+ ]);
+ }
+ }
+}
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 6b901f8..63e54f7 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -15,11 +15,17 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
- // User::factory(10)->create();
+ User::query()->firstOrCreate(
+ ['email' => 'test@example.com'],
+ [
+ 'name' => 'Test User',
+ 'password' => bcrypt('password'),
+ ]
+ );
- User::factory()->create([
- 'name' => 'Test User',
- 'email' => 'test@example.com',
+ $this->call([
+ RolePermissionSeeder::class,
+ ArticleSeeder::class,
]);
}
}
diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php
new file mode 100644
index 0000000..c9dfa88
--- /dev/null
+++ b/database/seeders/RolePermissionSeeder.php
@@ -0,0 +1,63 @@
+forgetCachedPermissions();
+
+ $permissions = [
+ 'write articles',
+ 'review content',
+ 'publish articles',
+ 'access admin',
+ ];
+
+ foreach ($permissions as $permissionName) {
+ Permission::query()->firstOrCreate([
+ 'name' => $permissionName,
+ 'guard_name' => 'web',
+ ]);
+ }
+
+ $writerRole = Role::query()->firstOrCreate([
+ 'name' => 'writer',
+ 'guard_name' => 'web',
+ ]);
+ $reviewerRole = Role::query()->firstOrCreate([
+ 'name' => 'reviewer',
+ 'guard_name' => 'web',
+ ]);
+ $publisherRole = Role::query()->firstOrCreate([
+ 'name' => 'publisher',
+ 'guard_name' => 'web',
+ ]);
+ $adminRole = Role::query()->firstOrCreate([
+ 'name' => 'admin',
+ 'guard_name' => 'web',
+ ]);
+
+ $writerRole->syncPermissions(['write articles']);
+ $reviewerRole->syncPermissions(['review content']);
+ $publisherRole->syncPermissions(['publish articles']);
+ $adminRole->syncPermissions($permissions);
+
+ $adminUser = User::query()->where('email', 'test@example.com')->first();
+ if ($adminUser) {
+ $adminUser->syncRoles(['admin']);
+ }
+
+ app(PermissionRegistrar::class)->forgetCachedPermissions();
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index e83b65b..04d438f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -10,7 +10,7 @@ services:
environment:
DB_CONNECTION: mysql
DB_HOST: mysql
- DB_PORT: 3306
+ DB_PORT: 3307
DB_DATABASE: presskit
DB_USERNAME: presskit
DB_PASSWORD: presskit123
@@ -36,7 +36,7 @@ services:
environment:
DB_CONNECTION: mysql
DB_HOST: mysql
- DB_PORT: 3306
+ DB_PORT: 3307
DB_DATABASE: presskit
DB_USERNAME: presskit
DB_PASSWORD: presskit123
@@ -62,7 +62,7 @@ services:
environment:
DB_CONNECTION: mysql
DB_HOST: mysql
- DB_PORT: 3306
+ DB_PORT: 3307
DB_DATABASE: presskit
DB_USERNAME: presskit
DB_PASSWORD: presskit123
@@ -95,8 +95,9 @@ services:
mysql:
image: mysql:8.4
container_name: presskit_mysql
+ command: ["mysqld", "--port=3307"]
ports:
- - "3307:3306"
+ - "3307:3307"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: presskit
@@ -105,7 +106,7 @@ services:
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
- test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot"]
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-P3307", "-proot"]
interval: 10s
timeout: 5s
retries: 10
@@ -122,6 +123,19 @@ services:
networks:
- presskit_net
+ node:
+ image: node:22-alpine
+ container_name: presskit_node
+ working_dir: /var/www/html
+ command: ["sh", "-lc", "npm install && npm run dev -- --host 0.0.0.0 --port 5173"]
+ ports:
+ - "5173:5173"
+ volumes:
+ - ./:/var/www/html
+ - node_modules:/var/www/html/node_modules
+ networks:
+ - presskit_net
+
networks:
presskit_net:
driver: bridge
@@ -129,3 +143,4 @@ networks:
volumes:
mysql_data:
redis_data:
+ node_modules:
diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh
index 069f87b..d3396d0 100644
--- a/docker/php/entrypoint.sh
+++ b/docker/php/entrypoint.sh
@@ -2,7 +2,29 @@
set -e
if [ -f /var/www/html/artisan ]; then
+ # Ensure runtime directories exist and are writable for Laravel.
+ mkdir -p \
+ /var/www/html/storage/logs \
+ /var/www/html/storage/framework/cache \
+ /var/www/html/storage/framework/sessions \
+ /var/www/html/storage/framework/views \
+ /var/www/html/bootstrap/cache
+
chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache || true
+ chmod -R a+rwX /var/www/html/storage /var/www/html/bootstrap/cache || true
+
+ # Keep log file writable even when bind-mounted from host.
+ touch /var/www/html/storage/logs/laravel.log || true
+ chown www-data:www-data /var/www/html/storage/logs/laravel.log || true
+ chmod 666 /var/www/html/storage/logs/laravel.log || true
+
+ # Non-fatal startup helpers for local/dev convenience.
+ php /var/www/html/artisan storage:link --force >/dev/null 2>&1 || true
+ php /var/www/html/artisan optimize:clear >/dev/null 2>&1 || true
+
+ # Re-apply ownership after artisan writes runtime files.
+ chown -R www-data:www-data /var/www/html/bootstrap/cache /var/www/html/storage/framework/cache || true
+ chmod -R a+rwX /var/www/html/bootstrap/cache /var/www/html/storage/framework/cache || true
fi
exec "$@"
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..fd57e12
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2432 @@
+{
+ "name": "html",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.0.0",
+ "axios": "^1.11.0",
+ "concurrently": "^9.0.1",
+ "laravel-vite-plugin": "^2.0.0",
+ "lightgallery": "^2.8.3",
+ "tailwindcss": "^4.0.0",
+ "vite": "^7.0.7"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.31.1",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
+ "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
+ "tailwindcss": "4.2.1"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.13.6",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
+ "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
+ "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/laravel-vite-plugin": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.1.0.tgz",
+ "integrity": "sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "vite-plugin-full-reload": "^1.1.0"
+ },
+ "bin": {
+ "clean-orphaned-assets": "bin/clean.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^7.0.0"
+ }
+ },
+ "node_modules/lightgallery": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/lightgallery/-/lightgallery-2.9.0.tgz",
+ "integrity": "sha512-58Ud1DyhD2ao58t+kPEqSZrjFxg23tGd5ZKr75erm7q31g5xhUtWUJH3sTUkhHzlyJAKHj5eTrJ37HQRXG4Wbg==",
+ "dev": true,
+ "license": "GPLv3",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.31.1",
+ "lightningcss-darwin-arm64": "1.31.1",
+ "lightningcss-darwin-x64": "1.31.1",
+ "lightningcss-freebsd-x64": "1.31.1",
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
+ "lightningcss-linux-arm64-gnu": "1.31.1",
+ "lightningcss-linux-arm64-musl": "1.31.1",
+ "lightningcss-linux-x64-gnu": "1.31.1",
+ "lightningcss-linux-x64-musl": "1.31.1",
+ "lightningcss-win32-arm64-msvc": "1.31.1",
+ "lightningcss-win32-x64-msvc": "1.31.1"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-full-reload": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz",
+ "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "node_modules/vite-plugin-full-reload/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 7686b29..fad1d81 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
+ "lightgallery": "^2.8.3",
"tailwindcss": "^4.0.0",
"vite": "^7.0.7"
}
diff --git a/presskit-internal.png b/presskit-internal.png
new file mode 100644
index 0000000..f53a41e
Binary files /dev/null and b/presskit-internal.png differ
diff --git a/public/images/no-content.jpg b/public/images/no-content.jpg
new file mode 100644
index 0000000..1d0a664
Binary files /dev/null and b/public/images/no-content.jpg differ
diff --git a/public/sitemap.xml b/public/sitemap.xml
new file mode 100644
index 0000000..2097dac
--- /dev/null
+++ b/public/sitemap.xml
@@ -0,0 +1,128 @@
+
+
+
+ https://presskit.internal
+ daily
+ 1.0
+
+
+ https://presskit.internal/articles/sample-article-11
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-20
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-19
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-18
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-17
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-16
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-15
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-14
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-13
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-12
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-10
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-09
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-08
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-07
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-06
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-05
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-04
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-03
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-02
+ 2026-03-10T04:11:48+00:00
+ daily
+ 0.8
+
+
+ https://presskit.internal/articles/sample-article-01
+ 2026-03-10T04:11:47+00:00
+ daily
+ 0.8
+
+
diff --git a/publish_article.md b/publish_article.md
new file mode 100644
index 0000000..3e3d75d
--- /dev/null
+++ b/publish_article.md
@@ -0,0 +1,20 @@
+# flow publish article
+
+Writer create article
+↓
+Draft
+↓
+Submit review
+↓
+Reviewer approve
+↓
+Publisher publish
+↓
+Queue job
+↓
+Generate static HTML
+Generate AMP
+Update Redis cache
+Purge CDN
+Update sitemap
+Ping search engine
\ No newline at end of file
diff --git a/resources/css/contact.css b/resources/css/contact.css
new file mode 100644
index 0000000..eff10fb
--- /dev/null
+++ b/resources/css/contact.css
@@ -0,0 +1,124 @@
+.contact-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 120;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ background: rgba(2, 6, 23, 0.62);
+ backdrop-filter: blur(2px);
+}
+
+.contact-modal.is-open {
+ display: flex;
+}
+
+.contact-modal__card {
+ width: min(460px, 100%);
+ border-radius: 1rem;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ padding: 1.25rem;
+ box-shadow: 0 20px 60px rgba(15, 23, 42, 0.28);
+}
+
+.dark .contact-modal__card {
+ background: #0f172a;
+ border-color: #334155;
+ color: #e2e8f0;
+}
+
+.contact-modal__title {
+ font-size: 1.125rem;
+ font-weight: 800;
+ color: #0f172a;
+}
+
+.contact-modal__header {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.contact-modal__icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ border-radius: 9999px;
+ background: #e2e8f0;
+ color: #334155;
+ flex-shrink: 0;
+}
+
+.contact-modal__icon.is-warning {
+ background: #fef3c7;
+ color: #b45309;
+}
+
+.contact-modal__icon.is-loading {
+ background: #dbeafe;
+ color: #1d4ed8;
+}
+
+.contact-modal__icon.is-success {
+ background: #d1fae5;
+ color: #047857;
+}
+
+.contact-modal__icon.is-fail {
+ background: #fee2e2;
+ color: #b91c1c;
+}
+
+.dark .contact-modal__title {
+ color: #f8fafc;
+}
+
+.contact-modal__message {
+ margin-top: 0.5rem;
+ color: #475569;
+ font-size: 0.95rem;
+ line-height: 1.6;
+}
+
+.dark .contact-modal__message {
+ color: #cbd5e1;
+}
+
+.contact-modal__actions {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.contact-modal__button {
+ border: 0;
+ border-radius: 0.65rem;
+ padding: 0.55rem 1rem;
+ background: #135bec;
+ color: #fff;
+ font-size: 0.875rem;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+.contact-modal__spinner {
+ width: 1rem;
+ height: 1rem;
+ border-radius: 9999px;
+ border: 2px solid #cbd5e1;
+ border-top-color: #135bec;
+ display: inline-block;
+ margin-right: 0.5rem;
+ animation: contact-spin 0.8s linear infinite;
+ vertical-align: -2px;
+}
+
+@keyframes contact-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/resources/css/guest.css b/resources/css/guest.css
new file mode 100644
index 0000000..5ee38ce
--- /dev/null
+++ b/resources/css/guest.css
@@ -0,0 +1,117 @@
+:root {
+ --guest-font-sans: 'Be Vietnam Pro', system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --guest-font-display: 'Merriweather', Georgia, serif;
+ --guest-size-body: 1rem;
+ --guest-size-body-lg: 1.125rem;
+ --guest-size-title: clamp(2rem, 2.8vw, 3rem);
+ --guest-size-subtitle: clamp(1.5rem, 2.2vw, 2.25rem);
+ --guest-size-card-title: clamp(1.25rem, 1.8vw, 1.75rem);
+ --guest-line-body: 1.75;
+}
+
+.guest-site {
+ font-family: var(--guest-font-sans);
+ font-size: var(--guest-size-body);
+ line-height: var(--guest-line-body);
+}
+
+.guest-content {
+ font-size: var(--guest-size-body);
+}
+
+.guest-title {
+ font-family: var(--guest-font-display);
+ font-size: var(--guest-size-title);
+ line-height: 1.18;
+ letter-spacing: -0.01em;
+}
+
+.guest-subtitle {
+ font-family: var(--guest-font-display);
+ font-size: var(--guest-size-subtitle);
+ line-height: 1.25;
+}
+
+.guest-card-title {
+ font-size: var(--guest-size-card-title);
+ line-height: 1.3;
+}
+
+.guest-lead {
+ font-size: var(--guest-size-body-lg);
+ line-height: 1.75;
+}
+
+.guest-text {
+ font-size: var(--guest-size-body);
+ line-height: var(--guest-line-body);
+}
+
+.guest-meta {
+ font-size: 0.875rem;
+ line-height: 1.5;
+}
+
+.guest-site input::placeholder,
+.guest-site textarea::placeholder {
+ color: rgb(148 163 184);
+ font-style: italic;
+ opacity: 1;
+}
+
+.dark .guest-site input::placeholder,
+.dark .guest-site textarea::placeholder {
+ color: rgb(148 163 184 / 0.78);
+}
+
+.article-image-shell {
+ position: relative;
+ overflow: hidden;
+ background: rgb(241 245 249);
+}
+
+.dark .article-image-shell {
+ background: rgb(30 41 59);
+}
+
+.article-image-shell::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(110deg, rgba(255, 255, 255, 0) 20%, rgba(255, 255, 255, 0.6) 45%, rgba(255, 255, 255, 0) 70%);
+ transform: translateX(-100%);
+ animation: article-image-shimmer 1.4s infinite;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.article-image-shell.is-loaded::before {
+ animation: none;
+ opacity: 0;
+}
+
+.article-lazy-image {
+ opacity: 0;
+ transition: opacity 0.35s ease;
+}
+
+.article-image-shell.is-loaded .article-lazy-image {
+ opacity: 1;
+}
+
+@keyframes article-image-shimmer {
+ to {
+ transform: translateX(100%);
+ }
+}
+
+@media (max-width: 768px) {
+ .guest-site {
+ font-size: 15px;
+ }
+
+ .guest-lead {
+ font-size: 1rem;
+ line-height: 1.7;
+ }
+}
diff --git a/resources/css/policy.css b/resources/css/policy.css
new file mode 100644
index 0000000..fcc1e79
--- /dev/null
+++ b/resources/css/policy.css
@@ -0,0 +1,28 @@
+.policy-toc-link {
+ color: #475569;
+}
+
+.policy-toc-link:hover {
+ background-color: #f1f5f9;
+ color: #0f766e;
+}
+
+.policy-toc-link.is-active {
+ background-color: #ccfbf1;
+ color: #0f766e;
+ font-weight: 600;
+}
+
+.dark .policy-toc-link {
+ color: #cbd5e1;
+}
+
+.dark .policy-toc-link:hover {
+ background-color: rgba(51, 65, 85, 0.6);
+ color: #5eead4;
+}
+
+.dark .policy-toc-link.is-active {
+ background-color: rgba(45, 212, 191, 0.16);
+ color: #5eead4;
+}
diff --git a/resources/js/app.js b/resources/js/app.js
index e59d6a0..404d1fe 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -1 +1,15 @@
import './bootstrap';
+import lightGallery from 'lightgallery';
+import 'lightgallery/css/lightgallery.css';
+
+document.addEventListener('DOMContentLoaded', function () {
+ const galleryContainers = document.querySelectorAll('[data-lightgallery]');
+
+ galleryContainers.forEach(function (container) {
+ lightGallery(container, {
+ selector: '.js-lg-item',
+ download: false,
+ counter: true,
+ });
+ });
+});
diff --git a/resources/js/contact.js b/resources/js/contact.js
new file mode 100644
index 0000000..8de5336
--- /dev/null
+++ b/resources/js/contact.js
@@ -0,0 +1,83 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const form = document.getElementById('contact-form');
+ const modal = document.getElementById('contact-status-modal');
+
+ if (!form || !modal) {
+ return;
+ }
+
+ const modalTitle = document.getElementById('contact-modal-title');
+ const modalMessage = document.getElementById('contact-modal-message');
+ const modalConfirm = document.getElementById('contact-modal-confirm');
+ const modalSpinner = document.getElementById('contact-modal-spinner');
+ const modalIcon = document.getElementById('contact-modal-icon');
+
+ const openModal = function (type, title, message) {
+ modalTitle.textContent = title;
+ modalMessage.textContent = message;
+
+ modalIcon.className = 'material-symbols-outlined contact-modal__icon';
+
+ if (type === 'warning') {
+ modalIcon.textContent = 'warning';
+ modalIcon.classList.add('is-warning');
+ } else if (type === 'loading') {
+ modalIcon.textContent = 'hourglass_top';
+ modalIcon.classList.add('is-loading');
+ } else if (type === 'success') {
+ modalIcon.textContent = 'check_circle';
+ modalIcon.classList.add('is-success');
+ } else {
+ modalIcon.textContent = 'error';
+ modalIcon.classList.add('is-fail');
+ }
+
+ const isLoading = type === 'loading';
+ modalSpinner.classList.toggle('hidden', !isLoading);
+ modalConfirm.classList.toggle('hidden', isLoading);
+
+ modal.classList.add('is-open');
+ modal.setAttribute('aria-hidden', 'false');
+ document.body.style.overflow = 'hidden';
+ };
+
+ const closeModal = function () {
+ modal.classList.remove('is-open');
+ modal.setAttribute('aria-hidden', 'true');
+ document.body.style.overflow = '';
+ };
+
+ modalConfirm.addEventListener('click', closeModal);
+
+ const validateRequiredFields = function () {
+ const requiredFields = form.querySelectorAll('[name="name"], [name="email"], [name="phone"], [name="subject"], [name="message"]');
+
+ for (const field of requiredFields) {
+ if (!String(field.value || '').trim()) {
+ field.focus();
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ form.addEventListener('submit', function (event) {
+ if (!validateRequiredFields()) {
+ event.preventDefault();
+ openModal('warning', 'Thiếu thông tin', 'Vui lòng nhập đầy đủ nội dung trước khi gửi tin nhắn.');
+ return;
+ }
+
+ openModal('loading', 'Đang gửi thông tin', 'Vui lòng đợi trong giây lát.');
+ });
+
+ const submitStatus = form.dataset.submitStatus || '';
+ const submitMessage = form.dataset.submitMessage || '';
+
+ if (submitStatus === 'success') {
+ openModal('success', 'Gửi thành công', submitMessage);
+ } else if (submitStatus === 'fail') {
+ openModal('fail', 'Gửi thất bại', submitMessage);
+ }
+});
diff --git a/resources/js/policy.js b/resources/js/policy.js
new file mode 100644
index 0000000..48990d7
--- /dev/null
+++ b/resources/js/policy.js
@@ -0,0 +1,68 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const nav = document.getElementById('policy-nav');
+ if (!nav) {
+ return;
+ }
+
+ const offsetTop = 80;
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ const navLinks = Array.from(nav.querySelectorAll('a[href^="#"]'));
+ const sections = navLinks
+ .map((link) => document.querySelector(link.getAttribute('href')))
+ .filter(Boolean);
+
+ const setActiveLink = function (hash) {
+ navLinks.forEach((link) => {
+ const isActive = link.getAttribute('href') === hash;
+ link.classList.toggle('is-active', isActive);
+ link.setAttribute('aria-current', isActive ? 'true' : 'false');
+ });
+ };
+
+ const initialHash = window.location.hash;
+ if (initialHash && nav.querySelector('a[href="' + initialHash + '"]')) {
+ setActiveLink(initialHash);
+ }
+
+ navLinks.forEach((link) => {
+ link.addEventListener('click', function (event) {
+ const hash = link.getAttribute('href');
+ const target = hash ? document.querySelector(hash) : null;
+ if (!target) {
+ return;
+ }
+
+ event.preventDefault();
+ setActiveLink(hash);
+
+ const targetTop = target.getBoundingClientRect().top + window.pageYOffset - offsetTop;
+ window.scrollTo({
+ top: Math.max(targetTop, 0),
+ behavior: prefersReducedMotion ? 'auto' : 'smooth',
+ });
+
+ history.replaceState(null, '', hash);
+ });
+ });
+
+ if ('IntersectionObserver' in window && sections.length > 0) {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ const visibleEntry = entries
+ .filter((entry) => entry.isIntersecting)
+ .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
+
+ if (visibleEntry && visibleEntry.target.id) {
+ setActiveLink('#' + visibleEntry.target.id);
+ }
+ },
+ {
+ root: null,
+ rootMargin: '-110px 0px -55% 0px',
+ threshold: 0.15,
+ }
+ );
+
+ sections.forEach((section) => observer.observe(section));
+ }
+});
diff --git a/resources/views/admin/articles/_form.blade.php b/resources/views/admin/articles/_form.blade.php
new file mode 100644
index 0000000..4743abb
--- /dev/null
+++ b/resources/views/admin/articles/_form.blade.php
@@ -0,0 +1,93 @@
+@csrf
+
+
+
+ Title
+
+
+
+
+ Category
+
+ -- Select category --
+ @foreach ($categories as $category)
+ category_id ?? '') === (string) $category->id)>
+ {{ $category->name }}
+
+ @endforeach
+
+
+
+
+ Excerpt
+
+
+
+
+ Content
+
+
+
+
+ Status
+ @php
+ $selectedStatus = old('status', $article->status?->value ?? \App\Enums\ArticleStatus::DRAFT->value);
+ @endphp
+
+ value)>Draft
+ value)>Published
+
+
+
+
+ Published at
+
+
+
+
+ SEO title
+
+
+
+
+ SEO description
+
+
+
+
+ SEO keywords (comma separated)
+
+
+
+
+ SEO OG image URL
+
+
+
+
+ Featured image
+
+
+
+
+ Gallery images
+
+
+
+
+ Attachments
+
+
+
+
+ Version files (for ArticleVersion)
+
+
+
+ Save article
+
diff --git a/resources/views/admin/articles/create.blade.php b/resources/views/admin/articles/create.blade.php
new file mode 100644
index 0000000..274777d
--- /dev/null
+++ b/resources/views/admin/articles/create.blade.php
@@ -0,0 +1,20 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Create Article')
+
+@section('content')
+ Create Article
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/articles/edit.blade.php b/resources/views/admin/articles/edit.blade.php
new file mode 100644
index 0000000..56304b1
--- /dev/null
+++ b/resources/views/admin/articles/edit.blade.php
@@ -0,0 +1,21 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Edit Article')
+
+@section('content')
+ Edit Article
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/articles/history.blade.php b/resources/views/admin/articles/history.blade.php
new file mode 100644
index 0000000..52aa67a
--- /dev/null
+++ b/resources/views/admin/articles/history.blade.php
@@ -0,0 +1,54 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Article History')
+
+@section('content')
+ Back to article
+ History: {{ $article->title }}
+
+
+
+
+ Time
+ Event
+ Causer
+ Old
+ New
+ Restore
+
+
+
+ @forelse ($activities as $activity)
+ @php
+ $old = $activity->properties['old'] ?? [];
+ $new = $activity->properties['attributes'] ?? [];
+ @endphp
+
+ {{ $activity->created_at?->toDateTimeString() }}
+ {{ $activity->event ?? 'updated' }}
+ {{ $activity->causer?->name ?? 'system' }}
+ {{ json_encode($old, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}
+ {{ json_encode($new, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}
+
+ @if (!empty($old))
+
+ @else
+ N/A
+ @endif
+
+
+ @empty
+
+ No activity log found for this article.
+
+ @endforelse
+
+
+
+
+ {{ $activities->links() }}
+
+@endsection
diff --git a/resources/views/admin/articles/index.blade.php b/resources/views/admin/articles/index.blade.php
new file mode 100644
index 0000000..185b367
--- /dev/null
+++ b/resources/views/admin/articles/index.blade.php
@@ -0,0 +1,49 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Articles')
+
+@section('content')
+ Articles
+
+ Create new article
+
+
+
+
+ Title
+ Category
+ Author
+ Status
+ Actions
+
+
+
+ @forelse ($articles as $article)
+
+ {{ $article->title }}
+ {{ $article->category?->name ?? 'N/A' }}
+ {{ $article->author?->name ?? 'N/A' }}
+ {{ ucfirst($article->status?->value ?? '') }}
+
+ View
+ Edit
+ History
+
+
+
+ @empty
+
+ No articles found.
+
+ @endforelse
+
+
+
+
+ {{ $articles->links() }}
+
+@endsection
diff --git a/resources/views/admin/articles/show.blade.php b/resources/views/admin/articles/show.blade.php
new file mode 100644
index 0000000..ae02840
--- /dev/null
+++ b/resources/views/admin/articles/show.blade.php
@@ -0,0 +1,127 @@
+@extends('admin.layouts.app')
+
+@section('title', $article->title)
+
+@section('head')
+ {!! \Artesaos\SEOTools\Facades\SEOTools::generate() !!}
+@endsection
+
+@section('content')
+ Back to list
+ View change history
+ Manage comments
+
+ {{ $article->title }}
+ Category: {{ $article->category?->name ?? 'N/A' }}
+ Author: {{ $article->author?->name ?? 'N/A' }}
+ Status: {{ ucfirst($article->status?->value ?? '') }}
+ Workflow: {{ ucfirst($article->workflow_status ?? 'draft') }}
+ Slug: {{ $article->slug }}
+
+ Publish Flow
+ @if ($article->workflow_status === 'draft')
+
+ @endif
+
+ @if ($article->workflow_status === 'review')
+
+ @endif
+
+ @if (in_array($article->workflow_status, ['approved', 'published'], true))
+
+ @endif
+
+ Submitted for review at: {{ $article->submitted_for_review_at?->toDateTimeString() ?? 'N/A' }}
+ Reviewed at: {{ $article->reviewed_at?->toDateTimeString() ?? 'N/A' }}
+ Published by: {{ $article->publisher?->name ?? 'N/A' }}
+ Static HTML path: {{ $article->static_html_path ?? 'N/A' }}
+ AMP HTML path: {{ $article->amp_html_path ?? 'N/A' }}
+ Last publish job at: {{ $article->last_published_job_at?->toDateTimeString() ?? 'N/A' }}
+
+ SEO Analysis
+ Score: {{ $article->seo?->score ?? 0 }}/100
+ Analyzed at: {{ $article->seo?->last_analyzed_at?->toDateTimeString() ?? 'N/A' }}
+
+ @if (!empty($article->seo?->warnings))
+ Warnings
+
+ @foreach ($article->seo->warnings as $warning)
+ {{ $warning }}
+ @endforeach
+
+ @endif
+
+ @if (!empty($article->seo?->score_breakdown))
+ Breakdown
+
+ @foreach ($article->seo->score_breakdown as $criterion => $point)
+ {{ str_replace('_', ' ', ucfirst($criterion)) }}: {{ $point }}
+ @endforeach
+
+ @endif
+
+ Change Slug
+
+ Published at: {{ $article->published_at?->toDateTimeString() ?? 'N/A' }}
+
+ Excerpt
+ {{ $article->excerpt ?: 'N/A' }}
+
+ Featured Image
+ @if ($article->getFirstMediaUrl('featured_image'))
+
+ @else
+ N/A
+ @endif
+
+ Gallery
+ @if ($article->getMedia('gallery')->isNotEmpty())
+
+ @foreach ($article->getMedia('gallery') as $media)
+
+ @endforeach
+
+ @else
+ N/A
+ @endif
+
+ Attachments
+ @if ($article->getMedia('attachments')->isNotEmpty())
+
+ @foreach ($article->getMedia('attachments') as $media)
+ {{ $media->name }}
+ @endforeach
+
+ @else
+ N/A
+ @endif
+
+ @php($latestVersion = $article->versions()->latest('version_number')->first())
+ Latest Version Files
+ @if ($latestVersion && $latestVersion->getMedia('version_files')->isNotEmpty())
+
+ @foreach ($latestVersion->getMedia('version_files') as $media)
+ {{ $media->name }}
+ @endforeach
+
+ @else
+ N/A
+ @endif
+
+ Content
+ {!! nl2br(e($article->content)) !!}
+@endsection
diff --git a/resources/views/admin/categories/_form.blade.php b/resources/views/admin/categories/_form.blade.php
new file mode 100644
index 0000000..491d579
--- /dev/null
+++ b/resources/views/admin/categories/_form.blade.php
@@ -0,0 +1,28 @@
+@csrf
+
+
+
+ Name
+
+
+
+
+ Description
+
+
+
+
+ Status
+ @php
+ $selectedStatus = old('status', $category->status?->value ?? \App\Enums\CategoryStatus::ACTIVE->value);
+ @endphp
+
+ value)>Active
+ value)>Inactive
+
+
+
+
Slug is generated automatically and kept stable after creation.
+
+
Save category
+
diff --git a/resources/views/admin/categories/create.blade.php b/resources/views/admin/categories/create.blade.php
new file mode 100644
index 0000000..53dfba3
--- /dev/null
+++ b/resources/views/admin/categories/create.blade.php
@@ -0,0 +1,20 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Create Category')
+
+@section('content')
+ Create Category
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/categories/edit.blade.php b/resources/views/admin/categories/edit.blade.php
new file mode 100644
index 0000000..446d865
--- /dev/null
+++ b/resources/views/admin/categories/edit.blade.php
@@ -0,0 +1,21 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Edit Category')
+
+@section('content')
+ Edit Category
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/categories/index.blade.php b/resources/views/admin/categories/index.blade.php
new file mode 100644
index 0000000..830253b
--- /dev/null
+++ b/resources/views/admin/categories/index.blade.php
@@ -0,0 +1,45 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Categories')
+
+@section('content')
+ Categories
+
+ Create new category
+
+
+
+
+ Name
+ Slug
+ Status
+ Actions
+
+
+
+ @forelse ($categories as $category)
+
+ {{ $category->name }}
+ {{ $category->slug }}
+ {{ ucfirst($category->status?->value ?? '') }}
+
+ Edit
+
+
+
+ @empty
+
+ No categories found.
+
+ @endforelse
+
+
+
+
+ {{ $categories->links() }}
+
+@endsection
diff --git a/resources/views/admin/comments/_form.blade.php b/resources/views/admin/comments/_form.blade.php
new file mode 100644
index 0000000..0bdfd42
--- /dev/null
+++ b/resources/views/admin/comments/_form.blade.php
@@ -0,0 +1,30 @@
+@csrf
+
+
+
+ User ID (optional)
+
+
+
+
+ Author name
+
+
+
+
+ Author email
+
+
+
+
+ Content
+
+
+
+
+ is_approved ?? false)) />
+ Approved
+
+
+ Save comment
+
diff --git a/resources/views/admin/comments/create.blade.php b/resources/views/admin/comments/create.blade.php
new file mode 100644
index 0000000..a8b15b2
--- /dev/null
+++ b/resources/views/admin/comments/create.blade.php
@@ -0,0 +1,20 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Create Comment')
+
+@section('content')
+ Create Comment for: {{ $article->title }}
+ Back to comments list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/comments/edit.blade.php b/resources/views/admin/comments/edit.blade.php
new file mode 100644
index 0000000..87b95ce
--- /dev/null
+++ b/resources/views/admin/comments/edit.blade.php
@@ -0,0 +1,21 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Edit Comment')
+
+@section('content')
+ Edit Comment #{{ $comment->id }} for: {{ $article->title }}
+ Back to comments list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/comments/index.blade.php b/resources/views/admin/comments/index.blade.php
new file mode 100644
index 0000000..ab627ed
--- /dev/null
+++ b/resources/views/admin/comments/index.blade.php
@@ -0,0 +1,53 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Comments - '.$article->title)
+
+@section('content')
+ Comments for: {{ $article->title }}
+
+
+ Back to article
+ |
+ Create new comment
+
+
+
+
+
+ ID
+ Author
+ Email
+ Content
+ Approved
+ Actions
+
+
+
+ @forelse ($comments as $comment)
+
+ {{ $comment->id }}
+ {{ $comment->author_name ?: ($comment->user?->name ?? 'N/A') }}
+ {{ $comment->author_email ?: ($comment->user?->email ?? 'N/A') }}
+ {{ \Illuminate\Support\Str::limit($comment->content, 120) }}
+ {{ $comment->is_approved ? 'Yes' : 'No' }}
+
+ Edit
+
+
+
+ @empty
+
+ No comments found.
+
+ @endforelse
+
+
+
+
+ {{ $comments->links() }}
+
+@endsection
diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php
new file mode 100644
index 0000000..bca1daf
--- /dev/null
+++ b/resources/views/admin/layouts/app.blade.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+ @yield('title', 'Admin')
+
+ @yield('head')
+
+
+ @if (session('status'))
+ {{ session('status') }}
+ @endif
+
+ @yield('content')
+
+
diff --git a/resources/views/admin/tags/_form.blade.php b/resources/views/admin/tags/_form.blade.php
new file mode 100644
index 0000000..8dc9d32
--- /dev/null
+++ b/resources/views/admin/tags/_form.blade.php
@@ -0,0 +1,12 @@
+@csrf
+
+
+
+ Name
+
+
+
+
Slug is generated automatically when creating a tag.
+
+
Save tag
+
diff --git a/resources/views/admin/tags/create.blade.php b/resources/views/admin/tags/create.blade.php
new file mode 100644
index 0000000..1190e70
--- /dev/null
+++ b/resources/views/admin/tags/create.blade.php
@@ -0,0 +1,20 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Create Tag')
+
+@section('content')
+ Create Tag
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/tags/edit.blade.php b/resources/views/admin/tags/edit.blade.php
new file mode 100644
index 0000000..f3fb712
--- /dev/null
+++ b/resources/views/admin/tags/edit.blade.php
@@ -0,0 +1,21 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Edit Tag')
+
+@section('content')
+ Edit Tag
+ Back to list
+
+ @if ($errors->any())
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+ @endif
+
+
+@endsection
diff --git a/resources/views/admin/tags/index.blade.php b/resources/views/admin/tags/index.blade.php
new file mode 100644
index 0000000..d05052d
--- /dev/null
+++ b/resources/views/admin/tags/index.blade.php
@@ -0,0 +1,43 @@
+@extends('admin.layouts.app')
+
+@section('title', 'Tags')
+
+@section('content')
+ Tags
+
+ Create new tag
+
+
+
+
+ Name
+ Slug
+ Actions
+
+
+
+ @forelse ($tags as $tag)
+
+ {{ $tag->name }}
+ {{ $tag->slug }}
+
+ Edit
+
+
+
+ @empty
+
+ No tags found.
+
+ @endforelse
+
+
+
+
+ {{ $tags->links() }}
+
+@endsection
diff --git a/resources/views/guest/about.blade.php b/resources/views/guest/about.blade.php
new file mode 100644
index 0000000..64a9270
--- /dev/null
+++ b/resources/views/guest/about.blade.php
@@ -0,0 +1,135 @@
+@extends('guest.layouts.app')
+
+@section('title', 'Về chúng tôi | '.config('app.name'))
+
+@section('content')
+
+
+
+
+
+
Câu chuyện của chúng tôi
+
+
+ Cung cấp tin tức trung thực, khách quan và nhanh chóng cho độc giả toàn cầu.
+ Chúng tôi cam kết mang lại những góc nhìn đa chiều về các sự kiện quan trọng nhất thế giới.
+
+
+
+
+
+
+
+
+
+
+ Tại {{ config('app.name') }}, sứ mệnh của chúng tôi là mang đến những thông tin chính xác,
+ kịp thời và có chiều sâu. Chúng tôi tin rằng báo chí chất lượng là nền tảng
+ của một xã hội thông tin minh bạch.
+
+
+ Mỗi bài viết đều trải qua quy trình kiểm chứng nghiêm ngặt để đảm bảo tính xác thực cao nhất trước khi đến tay độc giả.
+
+
+ verified Xác thực nguồn tin đa lớp
+ verified Phân tích khách quan, không định kiến
+ verified Cập nhật thời gian thực 24/7
+
+
+
+
+
+
+
+
+
+
+
5M+
+
Độc giả hàng ngày
+
+
+
15+
+
Năm kinh nghiệm
+
+
+
120+
+
Giải thưởng báo chí
+
+
+
50+
+
Văn phòng đại diện
+
+
+
+
+
+
+
+
+
Những người đứng sau những bản tin chất lượng, làm việc không mệt mỏi để mang đến sự thật cho độc giả.
+
+
+
+
+
+
+
+ Lê Minh Tuấn
+ Tổng biên tập
+
+
+
+
+
+ Nguyễn Thu Thủy
+ Phó tổng biên tập
+
+
+
+
+
+ Trần Quốc Hùng
+ Trưởng ban Quốc tế
+
+
+
+
+
+ Phạm Minh Anh
+ Trưởng ban Công nghệ
+
+
+
+
+
+
+
+
+
Hãy là người đầu tiên nhận được những tin tức quan trọng nhất và các phân tích chuyên sâu hàng tuần.
+
+
+
+@endsection
diff --git a/resources/views/guest/articles/index.blade.php b/resources/views/guest/articles/index.blade.php
new file mode 100644
index 0000000..85f59c9
--- /dev/null
+++ b/resources/views/guest/articles/index.blade.php
@@ -0,0 +1,135 @@
+@extends('guest.layouts.app')
+
+@section('title', $pageTitle ?? 'Tin tức mới nhất')
+
+@section('content')
+@php
+ $heading = str_replace(['Danh muc: ', 'Chu de: '], '', $pageTitle ?? 'Tin tức mới nhất');
+ $currentSort = $filters['sort'] ?? 'latest';
+@endphp
+
+
+
+
+
+
+
Cập nhật tin tức cuộc tế mới nhất, phân tích sâu sắc về chính trị, kinh tế và những biến động toàn cầu.
+
+
+
+
+
+
+
+ @if ($featuredArticle)
+
+
+
+
+ Tiêu điểm
+
+
+
+ {{ $featuredArticle->excerpt ?: \Illuminate\Support\Str::limit(strip_tags($featuredArticle->content), 190) }}
+
+ {{ $featuredArticle->author?->name ?? config('app.name') }}
+ calendar_today {{ optional($featuredArticle->published_at)->format('d/m/Y') ?? 'Mới cập nhật' }}
+ chat_bubble {{ $featuredArticle->comments_count ?? 24 }}
+
+
+ @endif
+
+
+ @forelse ($articles as $article)
+
+
+
+
+
+ {{ $article->excerpt ?: \Illuminate\Support\Str::limit(strip_tags($article->content), 140) }}
+ {{ $article->author?->name ?? config('app.name') }} · {{ optional($article->published_at)->format('d/m/Y') }}
+
+ @empty
+
Không có bài viết phù hợp.
+ @endforelse
+
+
+ @if ($articles instanceof \Illuminate\Pagination\LengthAwarePaginator)
+
+
+ chevron_left
+
+
+ @for ($i = 1; $i <= $articles->lastPage(); $i++)
+ @if ($i <= 3 || $i === $articles->lastPage() || abs($i - $articles->currentPage()) <= 1)
+
{{ $i }}
+ @elseif ($i === 4)
+
...
+ @endif
+ @endfor
+
+
+ chevron_right
+
+
+ @endif
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/articles/search.blade.php b/resources/views/guest/articles/search.blade.php
new file mode 100644
index 0000000..70b7241
--- /dev/null
+++ b/resources/views/guest/articles/search.blade.php
@@ -0,0 +1,139 @@
+@extends('guest.layouts.app')
+
+@section('title', "Kết quả tìm kiếm: {$searchKeyword} | ".config('app.name'))
+
+@section('content')
+
+
+
+
+
+
+
+
+ Tất cả
+ Bài viết
+ Video
+ Podcast
+
+
+
+
+ @forelse ($articles as $article)
+
+
+
+
+
+
+
+ {{ $article->category?->name ?? 'Tin tức' }}
+
+
+
+ {{ $article->excerpt ?: \Illuminate\Support\Str::limit(strip_tags($article->content), 180) }}
+
+
+ calendar_today
+ {{ optional($article->published_at)->format('d/m/Y') ?? 'Mới cập nhật' }}
+ •
+ {{ max(1, (int) ceil(str_word_count(strip_tags((string) $article->content)) / 220)) }} phút đọc
+
+
+
+ @empty
+
+ Không tìm thấy bài viết phù hợp với từ khóa {{ $searchKeyword }} .
+
+ @endforelse
+
+
+ @if ($articles instanceof \Illuminate\Pagination\LengthAwarePaginator)
+
+
+ chevron_left
+
+
+ @for ($i = 1; $i <= $articles->lastPage(); $i++)
+ @if ($i <= 3 || $i === $articles->lastPage() || abs($i - $articles->currentPage()) <= 1)
+
{{ $i }}
+ @elseif ($i === 4)
+
...
+ @endif
+ @endfor
+
+
+ chevron_right
+
+
+ @endif
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/articles/show.blade.php b/resources/views/guest/articles/show.blade.php
new file mode 100644
index 0000000..2b77f38
--- /dev/null
+++ b/resources/views/guest/articles/show.blade.php
@@ -0,0 +1,161 @@
+@extends('guest.layouts.app')
+
+@section('title', $article->seo?->title ?: $article->title)
+
+@section('head')
+ {!! \Artesaos\SEOTools\Facades\SEOTools::generate() !!}
+@endsection
+
+@section('content')
+
+
+ Trang chủ
+ ›
+ @if ($article->category?->slug)
+ {{ $article->category->name }}
+ ›
+ @endif
+ Chi tiết bài viết
+
+
+
+
+
+
+
+
+
+
+
{{ $article->author?->name ?? config('app.name') }}
+
{{ optional($article->published_at)->format('d/m/Y') ?? optional($article->created_at)->format('d/m/Y') }} · {{ max(3, (int) str_word_count(strip_tags((string) $article->content)) / 220) }} phút đọc
+
+
+
+
+ share
+ Chia sẻ
+
+
+ bookmark
+
+
+
+
+
+
+
+
+ {{ $article->excerpt ?: 'Bài viết chuyên sâu về xu hướng và tác động trong bối cảnh hiện tại.' }}
+
+
+
+
+ {!! nl2br(e((string) \Illuminate\Support\Str::limit(strip_tags($article->content), 2200, '...'))) !!}
+
+
+
+
+ Nội dung được biên tập theo hướng cân bằng giữa tốc độ cập nhật và độ chính xác thông tin,
+ đồng thời đặt trọng tâm vào bối cảnh, dữ liệu và tác động dài hạn đối với xã hội.
+
+
+
+ "Báo chí chất lượng không chỉ cung cấp thông tin mà còn giúp độc giả hiểu đúng bản chất của sự kiện."
+
+
+
+
+ Chúng tôi ưu tiên tiêu chuẩn xác minh nhiều lớp, nguồn trích dẫn rõ ràng và quy trình biên tập độc lập,
+ nhằm giảm thiểu nhiễu thông tin và tăng giá trị tham khảo cho người đọc.
+
+
+
+
+ @forelse ($article->tags as $tag)
+
{{ $tag->name }}
+ @empty
+
Tin tức
+ @endforelse
+
+
+
+
+ forum
+ Bình luận ({{ $article->comments_count ?? 0 }})
+
+
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/contact.blade.php b/resources/views/guest/contact.blade.php
new file mode 100644
index 0000000..a0f313b
--- /dev/null
+++ b/resources/views/guest/contact.blade.php
@@ -0,0 +1,139 @@
+@extends('guest.layouts.app')
+
+@section('title', 'Liên hệ | '.config('app.name'))
+
+@section('head')
+@vite(['resources/css/contact.css', 'resources/js/contact.js'])
+@endsection
+
+@section('content')
+
+
+
+
+
+ Chúng tôi luôn sẵn lòng lắng nghe ý kiến đóng góp, phản hồi và tin tức từ bạn.
+ Hãy kết nối với {{ config('app.name') }} qua các kênh dưới đây.
+
+
+
+
+
+
+
+
+ location_on
+
+ Trụ sở chính
+ Phường Phú Định, TP. Hồ Chí Minh, Việt Nam
+
+
+
+
+ call
+
+ Số điện thoại
+ 024 1234 5678 (Tổng đài) 024 8765 4321 (Hotline)
+
+
+
+
+ mail
+
+ Email Tòa soạn
+ support@presskit.vn
+
+
+
+
+ web
+
+ Email Quảng cáo
+ support@presskit.vn
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/home/index.blade.php b/resources/views/guest/home/index.blade.php
new file mode 100644
index 0000000..e71db84
--- /dev/null
+++ b/resources/views/guest/home/index.blade.php
@@ -0,0 +1,121 @@
+@extends('guest.layouts.app')
+
+@section('title', 'Tin tuc moi nhat')
+
+@section('content')
+
+ @if ($featuredArticle)
+
+
+
+
+
+
Nổi bật
+
+
{{ $featuredArticle->excerpt ?: \Illuminate\Support\Str::limit(strip_tags($featuredArticle->content), 170) }}
+
+ Đọc ngay arrow_forward
+
+
+
+
+ @endif
+
+
+
+
+
+
+ @forelse ($articles as $article)
+
+
+
+
+
+
+ @if ($article->category?->slug)
+
{{ $article->category->name }}
+ @else
+
Tin tức
+ @endif
+
+ schedule
+ {{ $article->published_at?->diffForHumans() ?? 'Vừa xong' }}
+
+
+
+
{{ $article->excerpt ?: \Illuminate\Support\Str::limit(strip_tags($article->content), 170) }}
+
+
+ @empty
+
Không có bài viết mới.
+ @endforelse
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/layouts/app.blade.php b/resources/views/guest/layouts/app.blade.php
new file mode 100644
index 0000000..a7cfba8
--- /dev/null
+++ b/resources/views/guest/layouts/app.blade.php
@@ -0,0 +1,20 @@
+
+
+
+
+
+ @yield('title', config('app.name'))
+ @include('guest.layouts.partials.styles')
+ @include('guest.layouts.partials.scripts')
+ @yield('head')
+
+
+ @include('guest.layouts.partials.header')
+
+
+ @yield('content')
+
+
+ @include('guest.layouts.partials.footer')
+
+
diff --git a/resources/views/guest/layouts/partials/footer.blade.php b/resources/views/guest/layouts/partials/footer.blade.php
new file mode 100644
index 0000000..b0e9b8e
--- /dev/null
+++ b/resources/views/guest/layouts/partials/footer.blade.php
@@ -0,0 +1,59 @@
+
+
+
+ keyboard_arrow_up
+
\ No newline at end of file
diff --git a/resources/views/guest/layouts/partials/header.blade.php b/resources/views/guest/layouts/partials/header.blade.php
new file mode 100644
index 0000000..8b8e634
--- /dev/null
+++ b/resources/views/guest/layouts/partials/header.blade.php
@@ -0,0 +1,40 @@
+
+
+
+
+
+ newspaper
+
+
+
+
+ @foreach (($guestNavCategories ?? collect()) as $menuCategory)
+ @php
+ $isActiveCategory = request()->routeIs('categories.articles') && request()->route('slug') === $menuCategory->slug;
+ @endphp
+
+ {{ $menuCategory->name }}
+
+ @endforeach
+
+
+
+
+
+
+ search
+
+
+
+
+
+ dark_mode
+ light_mode
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/guest/layouts/partials/scripts.blade.php b/resources/views/guest/layouts/partials/scripts.blade.php
new file mode 100644
index 0000000..3ca644b
--- /dev/null
+++ b/resources/views/guest/layouts/partials/scripts.blade.php
@@ -0,0 +1,80 @@
+
+
\ No newline at end of file
diff --git a/resources/views/guest/layouts/partials/styles.blade.php b/resources/views/guest/layouts/partials/styles.blade.php
new file mode 100644
index 0000000..42345da
--- /dev/null
+++ b/resources/views/guest/layouts/partials/styles.blade.php
@@ -0,0 +1,14 @@
+
+
+
+@vite(['resources/css/guest.css', 'resources/js/app.js'])
+
\ No newline at end of file
diff --git a/resources/views/guest/policy.blade.php b/resources/views/guest/policy.blade.php
new file mode 100644
index 0000000..60e739b
--- /dev/null
+++ b/resources/views/guest/policy.blade.php
@@ -0,0 +1,106 @@
+@extends('guest.layouts.app')
+
+@section('title', 'Chính sách bảo mật | '.config('app.name'))
+
+@section('head')
+@vite(['resources/css/policy.css', 'resources/js/policy.js'])
+@endsection
+
+@section('content')
+
+
+
+
+ Cập nhật lần cuối: 24 tháng 5, 2024
+
+
+
+
+
+
+
+ Tại {{ config('app.name') }}, chúng tôi cam kết bảo vệ quyền riêng tư và thông tin cá nhân của bạn.
+ Chính sách này giải thích cách chúng tôi thu thập, sử dụng và bảo vệ dữ liệu của bạn khi bạn truy cập trang web của chúng tôi.
+
+
+
+
+
+ Chúng tôi có thể thu thập các loại thông tin sau từ người dùng của mình:
+
+ check_circle Thông tin định danh: Tên, địa chỉ email, và thông tin liên hệ khi bạn đăng ký nhận bản tin hoặc gửi phản hồi.
+ check_circle Dữ liệu kỹ thuật: Địa chỉ IP, loại trình duyệt, hệ điều hành và thông tin thiết bị được thu thập tự động.
+ check_circle Dữ liệu sử dụng: Thông tin về cách bạn tương tác với các bài viết và chuyên mục của chúng tôi.
+
+
+
+
+
+ Chúng tôi sử dụng cookie để nâng cao trải nghiệm người dùng và phân tích lưu lượng truy cập trang web:
+
+
+
Cookie thiết yếu
+
Bắt buộc để trang web hoạt động bình thường và bảo mật tài khoản.
+
+
+
Cookie phân tích
+
Giúp chúng tôi hiểu nội dung nào được người dùng quan tâm nhất.
+
+
+
+
+
+
+ Bạn có các quyền sau đối với thông tin cá nhân của mình:
+
+ 1 Quyền truy cập Yêu cầu bản sao dữ liệu cá nhân mà chúng tôi đang lưu trữ.
+ 2 Quyền chỉnh sửa Yêu cầu sửa đổi bất kỳ thông tin nào không chính xác hoặc không đầy đủ.
+ 3 Quyền xóa dữ liệu Yêu cầu chúng tôi xóa dữ liệu cá nhân của bạn trong một số trường hợp nhất định.
+
+
+
+
+
+ Chúng tôi áp dụng các biện pháp bảo mật tiêu chuẩn ngành, bao gồm mã hóa SSL và tường lửa, để bảo vệ thông tin cá nhân của bạn khỏi bị truy cập, tiết lộ hoặc phá hủy trái phép. Tuy nhiên, không có phương thức truyền tin qua Internet nào là an toàn 100%.
+
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/guest/term.blade.php b/resources/views/guest/term.blade.php
new file mode 100644
index 0000000..daf2232
--- /dev/null
+++ b/resources/views/guest/term.blade.php
@@ -0,0 +1,51 @@
+@extends('guest.layouts.app')
+
+@section('title', 'Điều khoản sử dụng | '.config('app.name'))
+
+@section('content')
+
+
+
+
+ Quyền riêng tư và quyền lợi của bạn rất quan trọng với chúng tôi. Vui lòng đọc các chính sách được cập nhật dưới đây.
+ Cập nhật lần cuối: 24 tháng 10, 2023
+
+
+
+
+
+ Chào mừng bạn đến với {{ config('app.name') }}. Khi truy cập hoặc sử dụng dịch vụ của chúng tôi, bạn đồng ý tuân thủ các Điều khoản sử dụng này. Nếu bạn không đồng ý với toàn bộ điều khoản và điều kiện được nêu tại đây, bạn không được phép sử dụng trang web hoặc dịch vụ.
+
+
+
+
+ Bạn được phép tải tạm thời một bản sao tài liệu (thông tin hoặc phần mềm) trên website {{ config('app.name') }} chỉ để xem cá nhân, phi thương mại và trong thời gian ngắn.
+
+ 1 Chỉnh sửa hoặc sao chép tài liệu cho mục đích thương mại.
+ 2 Sử dụng tài liệu cho bất kỳ hình thức trình chiếu công khai nào (thương mại hoặc phi thương mại).
+ 3 Cố gắng dịch ngược, giải mã hoặc can thiệp vào bất kỳ phần mềm nào có trên website.
+
+
+
+
+
+ Các tài liệu xuất hiện trên website {{ config('app.name') }} có thể bao gồm lỗi kỹ thuật, lỗi đánh máy hoặc lỗi hình ảnh. {{ config('app.name') }} không đảm bảo rằng mọi nội dung trên website luôn chính xác, đầy đủ hoặc cập nhật tại mọi thời điểm. Chúng tôi có thể thay đổi nội dung trên website bất cứ lúc nào mà không cần thông báo trước.
+
+
+
+
+ {{ config('app.name') }} không xem xét toàn bộ các trang web được liên kết từ website của mình và không chịu trách nhiệm về nội dung của các trang đó. Việc xuất hiện liên kết không đồng nghĩa {{ config('app.name') }} xác nhận hoặc bảo trợ cho website liên kết. Người dùng tự chịu rủi ro khi truy cập các website bên ngoài.
+
+
+
+
+
Bạn có thắc mắc về điều khoản?
+
Nếu bạn có bất kỳ câu hỏi hoặc quan ngại nào liên quan đến tài liệu pháp lý của chúng tôi, vui lòng liên hệ bộ phận pháp chế.
+
+ mail
+ support@presskit.vn
+
+
+
+
+@endsection
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php
deleted file mode 100644
index b7355d7..0000000
--- a/resources/views/welcome.blade.php
+++ /dev/null
@@ -1,277 +0,0 @@
-
-
-
-
-
-
- {{ config('app.name', 'Laravel') }}
-
-
-
-
-
-
- @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
- @vite(['resources/css/app.css', 'resources/js/app.js'])
- @else
-
- @endif
-
-
-
- @if (Route::has('login'))
-
- @auth
-
- Dashboard
-
- @else
-
- Log in
-
-
- @if (Route::has('register'))
-
- Register
-
- @endif
- @endauth
-
- @endif
-
-
-
-
-
Let's get started
-
Laravel has an incredibly rich ecosystem. We suggest starting with the following.
-
-
-
-
- {{-- Laravel Logo --}}
-
-
-
-
-
-
-
-
-
-
- {{-- Light Mode 12 SVG --}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{-- Dark Mode 12 SVG --}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @if (Route::has('login'))
-
- @endif
-
-
diff --git a/routes/admin.php b/routes/admin.php
new file mode 100644
index 0000000..61b953e
--- /dev/null
+++ b/routes/admin.php
@@ -0,0 +1,21 @@
+name('admin.')->group(function (): void {
+ Route::get('articles/{article}/history', [ArticleController::class, 'history'])->name('articles.history');
+ Route::post('articles/{article}/history/{activity}/restore', [ArticleController::class, 'restore'])->name('articles.history.restore');
+ Route::patch('articles/{article}/slug', [ArticleController::class, 'updateSlug'])->name('articles.slug.update');
+ Route::post('articles/{article}/submit-review', [ArticleController::class, 'submitForReview'])->name('articles.submit-review');
+ Route::post('articles/{article}/approve', [ArticleController::class, 'approve'])->name('articles.approve');
+ Route::post('articles/{article}/publish', [ArticleController::class, 'publish'])->name('articles.publish');
+
+ Route::resource('articles', ArticleController::class);
+ Route::resource('articles.comments', CommentController::class)->except(['show']);
+ Route::resource('categories', CategoryController::class)->except(['show']);
+ Route::resource('tags', TagController::class)->except(['show']);
+});
diff --git a/routes/console.php b/routes/console.php
index 3c9adf1..452cd16 100644
--- a/routes/console.php
+++ b/routes/console.php
@@ -1,8 +1,13 @@
comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
+
+Schedule::command('sitemap:generate')->dailyAt('01:00');
+Schedule::command('hot:flush-views')->everyFiveMinutes();
+Schedule::command('hot:calculate-score')->hourly();
diff --git a/routes/web.php b/routes/web.php
index 86a06c5..c85c069 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,7 +1,45 @@
middleware('cacheResponse')
+ ->name('home');
+
+// Static guest pages from resources/views/guest/*
+Route::controller(PageController::class)->middleware('cacheResponse')->group(function (): void {
+ Route::get('about', 'about')->name('about');
+ Route::get('contact', 'contact')->name('contact');
+ Route::get('term', 'term')->name('term');
+ Route::get('policy', 'policy')->name('policy');
});
+
+Route::post('contact', [ContactSubmissionController::class, 'store'])
+ ->middleware('throttle:20,1')
+ ->name('contact.submit');
+
+// Article pages
+Route::get('articles', [ArticleController::class, 'index'])
+ ->middleware('cacheResponse')
+ ->name('articles.index');
+
+Route::get('chu-de/{slug}', [ArticleController::class, 'byTag'])
+ ->middleware('cacheResponse')
+ ->name('tags.articles');
+
+Route::get('{article}.html', [ArticleController::class, 'show'])
+ ->where('article', '[A-Za-z0-9\-]+')
+ ->middleware('cacheResponse')
+ ->name('articles.show');
+
+Route::get('{slug}', [ArticleController::class, 'byCategory'])
+ ->where('slug', '^(?!(about|contact|term|policy|articles|chu-de)$)[A-Za-z0-9\-]+')
+ ->middleware('cacheResponse')
+ ->name('categories.articles');
+
+require __DIR__.'/admin.php';
diff --git a/seo.md b/seo.md
new file mode 100644
index 0000000..4cb5f71
--- /dev/null
+++ b/seo.md
@@ -0,0 +1,55 @@
+# Các tiêu chí chấm điểm SEO
+Tiêu chí Điểm tối đa
+Title length 10
+Meta description 15
+Keyword in title 10
+Keyword in meta 10
+Keyword density 10
+Content length 10
+Heading structure 10
+Internal links 10
+Image alt 10
+URL slug 5
+
+=> Tổng điểm là 100
+
+# Score Title
+SEO tốt khi title 50–60 ký tự
+
+# Score Meta Description
+SEO tốt khi meta description 120–160 ký tự
+
+# Keyword trong Title
+
+# Keyword trong Meta
+
+# Keyword Density
+SEO tốt khoảng 1% – 2%
+
+# Content Length
+SEO tốt khi content dài hơn 800 từ
+
+# Heading Structure
+H1: 1 lần, chứa keyword chính
+H2: 2-3 lần, chứa keyword phụ
+H3: 3-5 lần, chứa keyword liên quan
+
+# Internal Links
+SEO tốt khi có >= 2 internal links trong bài viết
+
+# Image Alt
+SEO tốt khi tất cả hình ảnh đều có alt text chứa keyword
+
+# URL Slug
+SEO tốt khi URL slug ngắn gọn, chứa keyword chính, không có stop words (the, a, an, in, on, etc.) có độ dài từ 3-8 từ
+
+# workflow tối ưu SEO
+Editor viết bài
+ ↓
+Save article
+ ↓
+SEO Analyzer chạy
+ ↓
+Tính SEO Score
+ ↓
+Hiển thị warning
\ No newline at end of file
diff --git a/storage/app/.gitignore b/storage/app/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore
old mode 100644
new mode 100755
diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore
old mode 100644
new mode 100755
diff --git a/tech.md b/tech.md
new file mode 100644
index 0000000..bd442bd
--- /dev/null
+++ b/tech.md
@@ -0,0 +1,46 @@
+# Kiến trúc kỹ thuật
+Laravel
+ ↓
+MySQL (data)
+Redis (cache + ranking)
+Meilisearch (search)
+Queue + Horizon
+CDN
+
+# Danh sách model
+articles
+article_versions
+tags
+categories
+comments
+analytics
+trending
+ads
+
+# Danh sách Feature
+ ├ Article: Tạo, chỉnh sửa, xóa bài viết
+ ├ Category: Tìm kiếm bài viết theo category
+ ├ Tag: Tìm kiếm bài viết theo tag
+ ├ SEO: Tối ưu hóa bài viết cho công cụ tìm kiếm
+ ├ Analytics: Theo dõi lượt xem, tương tác của bài viết
+ ├ Trending: Hiển thị các bài viết đang hot
+ ├ Comment: Cho phép người dùng bình luận bài viết
+ ├ Media: Quản lý hình ảnh, video cho bài viết
+ ├ Ads: Quản lý quảng cáo hiển thị trên trang
+ ├ AI: Tự động gợi ý nội dung, tối ưu hóa SEO
+ ├ Search: Tìm kiếm bài viết theo từ khóa
+
+ # Recommended composer packages list
+ "laravel/sanctum": "^4.0",
+ "spatie/laravel-permission": "^6.0",
+ "spatie/laravel-activitylog": "^4.0",
+ "spatie/laravel-sluggable": "^3.0",
+ "spatie/laravel-medialibrary": "^11.0",
+ "spatie/laravel-sitemap": "^7.0",
+ "artesaos/seotools": "^1.2",
+ "laravel/scout": "^10.0",
+ "predis/predis": "^2.0",
+ "symfony/dom-crawler": "^7.0"
+
+ # command
+ - Clear response cache: php artisan responsecache:clear
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index f35b4e7..caf8f16 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -5,7 +5,15 @@ import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
laravel({
- input: ['resources/css/app.css', 'resources/js/app.js'],
+ input: [
+ 'resources/css/app.css',
+ 'resources/css/guest.css',
+ 'resources/css/contact.css',
+ 'resources/css/policy.css',
+ 'resources/js/app.js',
+ 'resources/js/contact.js',
+ 'resources/js/policy.js',
+ ],
refresh: true,
}),
tailwindcss(),