diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..8c6715a --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "laravel-boost": { + "command": "php", + "args": [ + "artisan", + "boost:mcp" + ] + } + } +} \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 103a6f3..b4af403 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,21 +9,10 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. - php - 8.4.16 -- laravel/fortify (FORTIFY) - v1 -- laravel/framework (LARAVEL) - v12 -- laravel/prompts (PROMPTS) - v0 -- livewire/flux (FLUXUI_FREE) - v2 -- livewire/livewire (LIVEWIRE) - v3 -- livewire/volt (VOLT) - v1 -- laravel/mcp (MCP) - v0 -- laravel/pint (PINT) - v1 -- laravel/sail (SAIL) - v1 -- pestphp/pest (PEST) - v4 -- phpunit/phpunit (PHPUNIT) - v12 - tailwindcss (TAILWINDCSS) - v4 ## Conventions -- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. +- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming. - Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. - Check for existing components to reuse before writing a new one. @@ -31,7 +20,7 @@ This application is a Laravel application and its main Laravel ecosystems packag - Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. ## Application Structure & Architecture -- Stick to existing directory structure - don't create new base folders without approval. +- Stick to existing directory structure; don't create new base folders without approval. - Do not change the application's dependencies without approval. ## Frontend Bundling @@ -43,17 +32,16 @@ This application is a Laravel application and its main Laravel ecosystems packag ## Documentation Files - You must only create documentation files if explicitly requested by the user. - === boost rules === ## Laravel Boost - Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. ## Artisan -- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters. +- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters. ## URLs -- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port. +- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port. ## Tinker / Debugging - You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. @@ -64,22 +52,21 @@ This application is a Laravel application and its main Laravel ecosystems packag - Only recent browser logs will be useful - ignore old logs. ## Searching Documentation (Critically Important) -- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. -- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. -- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches. +- Boost comes with a powerful `search-docs` tool you should use before any other approaches when dealing with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. +- The `search-docs` tool is perfect for all Laravel-related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. +- You must use this tool to search for Laravel ecosystem documentation before falling back to other approaches. - Search the documentation before making code changes to ensure we are taking the correct approach. -- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. -- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. +- Use multiple, broad, simple, topic-based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. +- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. ### Available Search Syntax - You can and should pass multiple queries at once. The most relevant results will be returned first. -1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth' -2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit" -3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order -4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit" -5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms - +1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'. +2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit". +3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order. +4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit". +5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms. === php rules === @@ -90,7 +77,7 @@ This application is a Laravel application and its main Laravel ecosystems packag ### Constructors - Use PHP 8 constructor property promotion in `__construct()`. - public function __construct(public GitHub $github) { } -- Do not allow empty `__construct()` methods with zero parameters. +- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private. ### Type Declarations - Always use explicit return type declarations for methods and functions. @@ -104,7 +91,7 @@ protected function isAccessible(User $user, ?string $path = null): bool ## Comments -- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on. +- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless there is something very complex going on. ## PHPDoc Blocks - Add useful array shape type definitions for arrays when appropriate. @@ -112,469 +99,44 @@ protected function isAccessible(User $user, ?string $path = null): bool ## Enums - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. - -=== herd rules === - -## Laravel Herd - -- The application is served by Laravel Herd and will be available at: https?://[kebab-case-project-dir].test. Use the `get-absolute-url` tool to generate URLs for the user to ensure valid URLs. -- You must not run any commands to make the site available via HTTP(s). It is _always_ available through Laravel Herd. - - === tests rules === ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. - - -=== laravel/core rules === - -## Do Things the Laravel Way - -- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `php artisan make:class`. -- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. - -### Database -- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. -- Use Eloquent models and relationships before suggesting raw database queries -- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. -- Generate code that prevents N+1 query problems by using eager loading. -- Use Laravel's query builder for very complex database operations. - -### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. - -### APIs & Eloquent Resources -- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. - -### Controllers & Validation -- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. -- Check sibling Form Requests to see if the application uses array or string based validation rules. - -### Queues -- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. - -### Authentication & Authorization -- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). - -### URL Generation -- When generating links to other pages, prefer named routes and the `route()` function. - -### Configuration -- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. - -### Testing -- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. -- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. - -### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. - - -=== laravel/v12 rules === - -## Laravel 12 - -- Use the `search-docs` tool to get version specific documentation. -- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. - -### Laravel 12 Structure -- No middleware files in `app/Http/Middleware/`. -- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. -- `bootstrap/providers.php` contains application specific service providers. -- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration. -- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration. - -### Database -- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. -- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. - -### Models -- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. - - -=== fluxui-free/core rules === - -## Flux UI Free - -- This project is using the free edition of Flux UI. It has full access to the free components and variants, but does not have access to the Pro components. -- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. -- You should use Flux UI components when available. -- Fallback to standard Blade components if Flux is unavailable. -- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project. -- Flux UI components look like this: - - - - - - -### Available Components -This is correct as of Boost installation, but there may be additional components within the codebase. - - -avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip - - - -=== livewire/core rules === - -## Livewire Core -- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components -- State should live on the server, with the UI reflecting it. -- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. - -## Livewire Best Practices -- Livewire components require a single root element. -- Use `wire:loading` and `wire:dirty` for delightful loading states. -- Add `wire:key` in loops: - - ```blade - @foreach ($items as $item) -
- {{ $item->name }} -
- @endforeach - ``` - -- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects: - - - public function mount(User $user) { $this->user = $user; } - public function updatedSearch() { $this->resetPage(); } - - - -## Testing Livewire - - - Livewire::test(Counter::class) - ->assertSet('count', 0) - ->call('increment') - ->assertSet('count', 1) - ->assertSee(1) - ->assertStatus(200); - - - - - $this->get('/posts/create') - ->assertSeeLivewire(CreatePost::class); - - - -=== livewire/v3 rules === - -## Livewire 3 - -### Key Changes From Livewire 2 -- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions. - - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. - - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). - - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). - - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). - -### New Directives -- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples. - -### Alpine -- Alpine is now included with Livewire, don't manually include Alpine.js. -- Plugins included with Alpine: persist, intersect, collapse, and focus. - -### Lifecycle Hooks -- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring: - - -document.addEventListener('livewire:init', function () { - Livewire.hook('request', ({ fail }) => { - if (fail && fail.status === 419) { - alert('Your session expired'); - } - }); - - Livewire.hook('message.failed', (message, component) => { - console.error(message); - }); -}); - - - -=== volt/core rules === - -## Livewire Volt - -- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. -- Make new Volt components using `php artisan make:volt [name] [--test] [--pest]` -- Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file -- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive. -- You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. - -### Volt Functional Component Example - - -@volt - 0]); - -$increment = fn () => $this->count++; -$decrement = fn () => $this->count--; - -$double = computed(fn () => $this->count * 2); -?> - -
-

Count: {{ $count }}

-

Double: {{ $this->double }}

- - -
-@endvolt -
- - -### Volt Class Based Component Example -To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax: - - - -use Livewire\Volt\Component; - -new class extends Component { - public $count = 0; - - public function increment() - { - $this->count++; - } -} ?> - -
-

{{ $count }}

- -
-
- - -### Testing Volt & Volt Components -- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`. - - -use Livewire\Volt\Volt; - -test('counter increments', function () { - Volt::test('counter') - ->assertSee('Count: 0') - ->call('increment') - ->assertSee('Count: 1'); -}); - - - - -declare(strict_types=1); - -use App\Models\{User, Product}; -use Livewire\Volt\Volt; - -test('product form creates product', function () { - $user = User::factory()->create(); - - Volt::test('pages.products.create') - ->actingAs($user) - ->set('form.name', 'Test Product') - ->set('form.description', 'Test Description') - ->set('form.price', 99.99) - ->call('create') - ->assertHasNoErrors(); - - expect(Product::where('name', 'Test Product')->exists())->toBeTrue(); -}); - - - -### Common Patterns - - - - null, 'search' => '']); - -$products = computed(fn() => Product::when($this->search, - fn($q) => $q->where('name', 'like', "%{$this->search}%") -)->get()); - -$edit = fn(Product $product) => $this->editing = $product->id; -$delete = fn(Product $product) => $product->delete(); - -?> - - - - - - - - - - - Save - Saving... - - - - -=== pint/core rules === - -## Laravel Pint Code Formatter - -- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. - - -=== pest/core rules === - -## Pest -### Testing -- If you need to verify a feature is working, write or update a Unit / Feature test. - -### Pest Tests -- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. -- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. -- Tests should test all of the happy paths, failure paths, and weird paths. -- Tests live in the `tests/Feature` and `tests/Unit` directories. -- Pest tests look and behave like this: - -it('is true', function () { - expect(true)->toBeTrue(); -}); - - -### Running Tests -- Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). -- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. - -### Pest Assertions -- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: - -it('returns all', function () { - $response = $this->postJson('/api/docs', []); - - $response->assertSuccessful(); -}); - - -### Mocking -- Mocking can be very helpful when appropriate. -- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. -- You can also create partial mocks using the same import or self method. - -### Datasets -- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules. - - -it('has emails', function (string $email) { - expect($email)->not->toBeEmpty(); -})->with([ - 'james' => 'james@laravel.com', - 'taylor' => 'taylor@laravel.com', -]); - - - -=== pest/v4 rules === - -## Pest 4 - -- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage. -- Browser testing is incredibly powerful and useful for this project. -- Browser tests should live in `tests/Browser/`. -- Use the `search-docs` tool for detailed guidance on utilizing these features. - -### Browser Testing -- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test. -- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test. -- If requested, test on multiple browsers (Chrome, Firefox, Safari). -- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints). -- Switch color schemes (light/dark mode) when appropriate. -- Take screenshots or pause tests for debugging when appropriate. - -### Example Tests - - -it('may reset the password', function () { - Notification::fake(); - - $this->actingAs(User::factory()->create()); - - $page = visit('/sign-in'); // Visit on a real browser... - - $page->assertSee('Sign In') - ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs() - ->click('Forgot Password?') - ->fill('email', 'nuno@laravel.com') - ->click('Send Reset Link') - ->assertSee('We have emailed your password reset link!') - - Notification::assertSent(ResetPassword::class); -}); - - - -$pages = visit(['/', '/about', '/contact']); - -$pages->assertNoJavascriptErrors()->assertNoConsoleLogs(); - - +- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter. === tailwindcss/core rules === -## Tailwind Core +## Tailwind CSS -- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own. -- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..) -- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically +- Use Tailwind CSS classes to style HTML; check and use existing Tailwind conventions within the project before writing your own. +- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc.). +- Think through class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child carefully to limit repetition, and group elements logically. - You can use the `search-docs` tool to get exact examples from the official documentation when needed. ### Spacing -- When listing items, use gap utilities for spacing, don't use margins. - - -
-
Superior
-
Michigan
-
Erie
-
-
+- When listing items, use gap utilities for spacing; don't use margins. + +
+
Superior
+
Michigan
+
Erie
+
+
### Dark Mode - If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. - === tailwindcss/v4 rules === -## Tailwind 4 +## Tailwind CSS 4 -- Always use Tailwind CSS v4 - do not use the deprecated utilities. +- Always use Tailwind CSS v4; do not use the deprecated utilities. - `corePlugins` is not supported in Tailwind v4. - In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed. + @theme { --color-brand: oklch(0.72 0.11 178); @@ -590,9 +152,8 @@ $pages->assertNoJavascriptErrors()->assertNoConsoleLogs(); + @import "tailwindcss"; - ### Replaced Utilities -- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement. +- Tailwind v4 removed deprecated utilities. Do not use the deprecated option; use the replacement. - Opacity values are still numeric. | Deprecated | Replacement | diff --git a/.gitignore b/.gitignore index 016d612..859dd6f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ yarn-error.log /.zed laradumps.yaml +.junie +.history +/temp +/public/sitemap.xml +_project diff --git a/Commands.md b/Commands.md new file mode 100644 index 0000000..4e332b2 --- /dev/null +++ b/Commands.md @@ -0,0 +1,86 @@ +## Development & Execution +- `npm run dev` Running Vite for local development +- `npm run build` Build assets for production +- `php artisan serve` Start the Laravel development server +- `php artisan tinker` Interact with your application in a REPL shell + +## Database & Migrations +- `php artisan migrate` Run outstanding migrations +- `php artisan make:migration [name]` Create a new migration file +- `php artisan make:migration [name] --table=[table]` Create a migration to modify an existing table +- `php artisan migrate:reset` Rollback all database migrations +- `php artisan migrate:rollback` Rollback the last database migration +- `php artisan migrate:fresh` Drop all tables and re-run all migrations +- `php artisan migrate:refresh` Rollback and re-run all migrations +- `php artisan migrate:refresh --seed --force` Rollback, re-run migrations, and seed the database in production +- `php artisan make:seeder SeederName` Create a new seeder class +- `php artisan db:seed` Seed the database with records + +## Generators (Controllers, Classes & Components) +- `php artisan make:controller ControllerName` Create a new controller +- `php artisan make:viewcomposer Name` Create a new view composer +- `php artisan make:component [name] --inline` Create a new inline Blade component +- `php artisan make:scope ProjectScope` Create a new Eloquent global scope +- `php artisan make:trait` Create a new Trait +- `php artisan make:command CustomTask` Create a new Artisan command +- `php artisan make:class CustomClass` Create a new PHP class + +## Livewire +- `php artisan make:livewire dir.name` Create a new Livewire component +- `php artisan make:livewire dir.name --inline` Create a new inline Livewire component +- `php artisan livewire:stubs` Publish Livewire stubs for customization +- `php artisan livewire:layout` Create a new Livewire layout file + +## Optimization & Maintenance +- `php artisan cache:clear` Flush the application cache +- `php artisan view:clear` Clear all compiled view files +- `php artisan config:clear` Remove the configuration cache file +- `php artisan route:clear` Remove the route cache file +- `composer dump-autoload` Regenerate the list of all classes that need to be included +- `php artisan config:show myapp` Display the contents of a configuration file + +## Routing & Scheduling +- `php artisan route:list` List all registered routes +- `php artisan schedule:run` Run the scheduled commands +- `php artisan schedule:work` Start the schedule worker (local development) +- `php artisan schedule:list` List the scheduled tasks + +## Custom Application Commands (Imports & Sync) +- `php artisan app:generate-album --url https://laravel-core.vades.dev/` Generate album from storage URL +- `php artisan app:import-project-content` Import project contents from markdown files +- `php artisan app:generate-sitemap laravel-core` Generate laravel-core.test sitemap +- `php artisan app:generate-sitemap ivnbg --path=../domains/ivnbg.com/public_html/` Generate ivnbg.com sitemap +# Preview files without downloading +php artisan github:download-publish-files --dry-run + +# Download all files +php artisan github:download-publish-files + +- `php artisan app:db-test` Run database connection/integrity test +- `php artisan app:import-project-content` Import project content data +- `php artisan app:generate-album --url [url]` Generate album from storage URL +- `php artisan app:generate-sitemap ivnbg --path=../domains/ivnbg.com/public_html ` Generate sitemap for ivnbg.com +- `php artisan app:generate-sitemap martinvach --path=../domains/martinvach.com/public_html ` Generate sitemap for martinvach.com +- `php artisan app:generate-sitemap vades --path=../domains/vades.dev/public_html ` Generate sitemap for vades.dev +- +- `php artisan app:csv-to-md` Convert CSV files to Markdown +- `php artisan app:sync-drive-to-local [path] [target]` Sync Google Drive folder to local +- `php artisan app:google-drive-synchronize [folder] [target]` Synchronize Google Drive assets +- `php artisan app:google-drive-download-files [src] [dest]` Download specific files from Google Drive + + +## Model Generation Flags +*Common flags used with `php artisan make:model ModelName`* + +- `-c`, `--controller` Create a new controller for the model +- `-f`, `--factory` Create a new factory for the model +- `--force` Create the class even if the model already exists +- `-m`, `--migration` Create a new migration file for the model +- `-s`, `--seed` Create a new seeder file for the model +- `-p`, `--pivot` Indicates if the model should be a custom intermediate table model +- `-r`, `--resource` Indicates if the controller should be a resource controller + +## Path Helpers +- `base_path();` /var/www/mysite +- `app_path();` /var/www/mysite/app +- `storage_path();` /var/www/mysite/storage diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..03b6ff9 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,25 @@ +# Deployment Instructions +This document provides step-by-step instructions for deploying the application to a production environment. +## Create symbolic link for storage +To ensure that images stored in the `storage` folder are accessible via the web, you need to create a symbolic link. Run the following command in your terminal: +```bash +php artisan storage:link +``` +This command creates a symbolic link from `public/storage` to `storage/app/public`, allowing you to access images stored in the storage folder through your web server. +### Verify the symbolic link +After running the command, verify that the symbolic link was created successfully by checking the contents of the `public` directory: +```bash +ls -la public/ +``` +You should see a line indicating that `storage` is a symbolic link pointing to `../storage/app/public`. +### Set correct permissions +Ensure that the web server has the correct permissions to read from the `storage` folder. You may need to adjust the permissions using the following command: +```bash +chmod -R 775 storage +``` +Make sure the web server user (e.g., `www-data` for Apache) has access to the `storage` directory. +### Test image access +To confirm that images can be accessed correctly, try to load an image stored in the `storage/app/public` directory via your web browser. The URL should look like this: +``` +http://your-domain.com/storage/images/your-image.jpg +``` \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..e25af94 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,523 @@ + +=== foundation rules === + +# Laravel Boost Guidelines + +The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. + +## Foundational Context +This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. + +- php - 8.4.16 +- laravel/framework (LARAVEL) - v12 +- laravel/prompts (PROMPTS) - v0 +- laravel/telescope (TELESCOPE) - v5 +- livewire/livewire (LIVEWIRE) - v4 +- livewire/volt (VOLT) - v1 +- laravel/mcp (MCP) - v0 +- laravel/pint (PINT) - v1 +- laravel/sail (SAIL) - v1 +- pestphp/pest (PEST) - v4 +- phpunit/phpunit (PHPUNIT) - v12 +- tailwindcss (TAILWINDCSS) - v4 + +## Conventions +- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming. +- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. +- Check for existing components to reuse before writing a new one. + +## Verification Scripts +- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. + +## Application Structure & Architecture +- Stick to existing directory structure; don't create new base folders without approval. +- Do not change the application's dependencies without approval. + +## Frontend Bundling +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. + +## Replies +- Be concise in your explanations - focus on what's important rather than explaining obvious details. + +## Documentation Files +- You must only create documentation files if explicitly requested by the user. + +=== boost rules === + +## Laravel Boost +- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. + +## Artisan +- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters. + +## URLs +- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port. + +## Tinker / Debugging +- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. +- Use the `database-query` tool when you only need to read from the database. + +## Reading Browser Logs With the `browser-logs` Tool +- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. +- Only recent browser logs will be useful - ignore old logs. + +## Searching Documentation (Critically Important) +- Boost comes with a powerful `search-docs` tool you should use before any other approaches when dealing with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. +- The `search-docs` tool is perfect for all Laravel-related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. +- You must use this tool to search for Laravel ecosystem documentation before falling back to other approaches. +- Search the documentation before making code changes to ensure we are taking the correct approach. +- Use multiple, broad, simple, topic-based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. +- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. + +### Available Search Syntax +- You can and should pass multiple queries at once. The most relevant results will be returned first. + +1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'. +2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit". +3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order. +4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit". +5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms. + +=== php rules === + +## PHP + +- Always use curly braces for control structures, even if it has one line. + +### Constructors +- Use PHP 8 constructor property promotion in `__construct()`. + - public function __construct(public GitHub $github) { } +- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private. + +### Type Declarations +- Always use explicit return type declarations for methods and functions. +- Use appropriate PHP type hints for method parameters. + + +protected function isAccessible(User $user, ?string $path = null): bool +{ + ... +} + + +## Comments +- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless there is something very complex going on. + +## PHPDoc Blocks +- Add useful array shape type definitions for arrays when appropriate. + +## Enums +- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. + +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter. + +=== laravel/core rules === + +## Do Things the Laravel Way + +- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `php artisan make:class`. +- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. + +### Database +- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. +- Use Eloquent models and relationships before suggesting raw database queries. +- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. +- Generate code that prevents N+1 query problems by using eager loading. +- Use Laravel's query builder for very complex database operations. + +### Model Creation +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. + +### APIs & Eloquent Resources +- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. + +### Controllers & Validation +- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. +- Check sibling Form Requests to see if the application uses array or string based validation rules. + +### Queues +- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. + +### Authentication & Authorization +- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). + +### URL Generation +- When generating links to other pages, prefer named routes and the `route()` function. + +### Configuration +- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. + +### Testing +- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. +- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. +- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. + +### Vite Error +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. + +=== laravel/v12 rules === + +## Laravel 12 + +- Use the `search-docs` tool to get version-specific documentation. +- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. + +### Laravel 12 Structure +- In Laravel 12, middleware are no longer registered in `app/Http/Kernel.php`. +- Middleware are configured declaratively in `bootstrap/app.php` using `Application::configure()->withMiddleware()`. +- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. +- `bootstrap/providers.php` contains application specific service providers. +- The `app\Console\Kernel.php` file no longer exists; use `bootstrap/app.php` or `routes/console.php` for console configuration. +- Console commands in `app/Console/Commands/` are automatically available and do not require manual registration. + +### Database +- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. +- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. + +### Models +- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. + +=== livewire/core rules === + +## Livewire + +- Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests. +- Use the `php artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. +- State should live on the server, with the UI reflecting it. +- All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions. + +## Livewire Best Practices +- Livewire components require a single root element. +- Use `wire:loading` and `wire:dirty` for delightful loading states. +- Add `wire:key` in loops: + + ```blade + @foreach ($items as $item) +
+ {{ $item->name }} +
+ @endforeach + ``` + +- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects: + + + public function mount(User $user) { $this->user = $user; } + public function updatedSearch() { $this->resetPage(); } + + +## Testing Livewire + + + Livewire::test(Counter::class) + ->assertSet('count', 0) + ->call('increment') + ->assertSet('count', 1) + ->assertSee(1) + ->assertStatus(200); + + + + $this->get('/posts/create') + ->assertSeeLivewire(CreatePost::class); + + +=== volt/core rules === + +## Livewire Volt + +- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. +- Make new Volt components using `php artisan make:volt [name] [--test] [--pest]`. +- Volt is a class-based and functional API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to coexist in the same file. +- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive. +- You must check existing Volt components to determine if they're functional or class-based. If you can't detect that, ask the user which they prefer before writing a Volt component. + +### Volt Functional Component Example + + +@volt + 0]); + +$increment = fn () => $this->count++; +$decrement = fn () => $this->count--; + +$double = computed(fn () => $this->count * 2); +?> + +
+

Count: {{ $count }}

+

Double: {{ $this->double }}

+ + +
+@endvolt +
+ +### Volt Class Based Component Example +To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax: + + +use Livewire\Volt\Component; + +new class extends Component { + public $count = 0; + + public function increment() + { + $this->count++; + } +} ?> + +
+

{{ $count }}

+ +
+
+ +### Testing Volt & Volt Components +- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`. + + +use Livewire\Volt\Volt; + +test('counter increments', function () { + Volt::test('counter') + ->assertSee('Count: 0') + ->call('increment') + ->assertSee('Count: 1'); +}); + + + +declare(strict_types=1); + +use App\Models\{User, Product}; +use Livewire\Volt\Volt; + +test('product form creates product', function () { + $user = User::factory()->create(); + + Volt::test('pages.products.create') + ->actingAs($user) + ->set('form.name', 'Test Product') + ->set('form.description', 'Test Description') + ->set('form.price', 99.99) + ->call('create') + ->assertHasNoErrors(); + + expect(Product::where('name', 'Test Product')->exists())->toBeTrue(); +}); + + +### Common Patterns + + + null, 'search' => '']); + +$products = computed(fn() => Product::when($this->search, + fn($q) => $q->where('name', 'like', "%{$this->search}%") +)->get()); + +$edit = fn(Product $product) => $this->editing = $product->id; +$delete = fn(Product $product) => $product->delete(); + +?> + + + + + + + + + + + Save + Saving... + + + +=== pint/core rules === + +## Laravel Pint Code Formatter + +- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. + +=== pest/core rules === + +## Pest +### Testing +- If you need to verify a feature is working, write or update a Unit / Feature test. + +### Pest Tests +- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. +- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. +- Tests should test all of the happy paths, failure paths, and weird paths. +- Tests live in the `tests/Feature` and `tests/Unit` directories. +- Pest tests look and behave like this: + +it('is true', function () { + expect(true)->toBeTrue(); +}); + + +### Running Tests +- Run the minimal number of tests using an appropriate filter before finalizing code edits. +- To run all tests: `php artisan test --compact`. +- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file). +- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. + +### Pest Assertions +- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: + +it('returns all', function () { + $response = $this->postJson('/api/docs', []); + + $response->assertSuccessful(); +}); + + +### Mocking +- Mocking can be very helpful when appropriate. +- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. +- You can also create partial mocks using the same import or self method. + +### Datasets +- Use datasets in Pest to simplify tests that have a lot of duplicated data. This is often the case when testing validation rules, so consider this solution when writing tests for validation rules. + + +it('has emails', function (string $email) { + expect($email)->not->toBeEmpty(); +})->with([ + 'james' => 'james@laravel.com', + 'taylor' => 'taylor@laravel.com', +]); + + +=== pest/v4 rules === + +## Pest 4 + +- Pest 4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage. +- Browser testing is incredibly powerful and useful for this project. +- Browser tests should live in `tests/Browser/`. +- Use the `search-docs` tool for detailed guidance on utilizing these features. + +### Browser Testing +- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest 4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test. +- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test. +- If requested, test on multiple browsers (Chrome, Firefox, Safari). +- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints). +- Switch color schemes (light/dark mode) when appropriate. +- Take screenshots or pause tests for debugging when appropriate. + +### Example Tests + + +it('may reset the password', function () { + Notification::fake(); + + $this->actingAs(User::factory()->create()); + + $page = visit('/sign-in'); // Visit on a real browser... + + $page->assertSee('Sign In') + ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs() + ->click('Forgot Password?') + ->fill('email', 'nuno@laravel.com') + ->click('Send Reset Link') + ->assertSee('We have emailed your password reset link!') + + Notification::assertSent(ResetPassword::class); +}); + + + +$pages = visit(['/', '/about', '/contact']); + +$pages->assertNoJavascriptErrors()->assertNoConsoleLogs(); + + +=== tailwindcss/core rules === + +## Tailwind CSS + +- Use Tailwind CSS classes to style HTML; check and use existing Tailwind conventions within the project before writing your own. +- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc.). +- Think through class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child carefully to limit repetition, and group elements logically. +- You can use the `search-docs` tool to get exact examples from the official documentation when needed. + +### Spacing +- When listing items, use gap utilities for spacing; don't use margins. + + +
+
Superior
+
Michigan
+
Erie
+
+
+ +### Dark Mode +- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. + +=== tailwindcss/v4 rules === + +## Tailwind CSS 4 + +- Always use Tailwind CSS v4; do not use the deprecated utilities. +- `corePlugins` is not supported in Tailwind v4. +- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed. + + +@theme { + --color-brand: oklch(0.72 0.11 178); +} + + +- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: + + + - @tailwind base; + - @tailwind components; + - @tailwind utilities; + + @import "tailwindcss"; + + +### Replaced Utilities +- Tailwind v4 removed deprecated utilities. Do not use the deprecated option; use the replacement. +- Opacity values are still numeric. + +| Deprecated | Replacement | +|------------+--------------| +| bg-opacity-* | bg-black/* | +| text-opacity-* | text-black/* | +| border-opacity-* | border-black/* | +| divide-opacity-* | divide-black/* | +| ring-opacity-* | ring-black/* | +| placeholder-opacity-* | placeholder-black/* | +| flex-shrink-* | shrink-* | +| flex-grow-* | grow-* | +| overflow-ellipsis | text-ellipsis | +| decoration-slice | box-decoration-slice | +| decoration-clone | box-decoration-clone | +
diff --git a/README.md b/README.md index b8e26ad..d8df5e6 100644 --- a/README.md +++ b/README.md @@ -1,82 +1 @@ -# 3rd party libraries used in the app -## laravel-google-drive-storage -This package allow to store and get data from google drive like S3 AWS in laravel -https://github.com/yaza-putu/laravel-google-drive-storage -# Run app with docker -```bash -docker compose up -d -``` -# Laravel quick commands -- `npm run dev` Running Vite -- `php artisan serve` Running Laravel -- `npm run build` Build for production -- `php artisan migrate` Run migrations -- `php artisan make:controller ControllerName` Create a new controller -- `php artisan route:list` List all routes -- `php artisan make:viewcomposer ViewComposerName` Create a new view composer -- `php artisan make:component web.features.blog --inline` Create a new component -- `php artisan make:livewire dir.component-name` Create a new Livewire component -- `php artisan make:livewire dir.component-name --inline` Create a new inline Livewire component -- `php artisan livewire:stubs` Publish Livewire stubs -- `php artisan livewire:layout` Create a new layout file -- `php artisan make:migration create_flights_table` Create a new migration file -- `php artisan make:migration add_status_to_flights_table --table=flights` Create a new migration file to modify an existing table -- `php artisan make:seeder SeederName` Create a new middleware -- `php artisan migrate:reset` -- `php artisan migrate:rollback` -- `php artisan migrate:fresh` Drop all tables and re-run all migrations -- `php artisan migrate:refresh` Rollback and re-run all migrations -- `php artisan migrate:refresh --seed --force` Rollback and re-run all migrations and seed the database -- `php artisan db:seed` -- `php artisan cache:clear` -- `php artisan view:clear` -- `php artisan config:clear` -- `php artisan route:clear` -- `composer dump-autoload` -- `php artisan tinker` -- `php artisan make:scope ProjectScope` -- `php artisan make:trait` -- `php artisan make:command CustomTask` -- `php artisan make:class CustomClass` -- `php artisan config:show myapp` -- `php artisan schedule:run` -- `php artisan schedule:work` -- `php artisan schedule:list` - -## Imports -IMPORTANT: USE `docker-compose exec larapi` php artisan app:db-test -- php artisan app:import-project --name martinvach -- php artisan app:generate-album --url https://www.ivnbg.com/storage/albums -- php artisan app:csv-to-md -- php artisan app:generate-ivnbg-sitemap -- php artisan app:generate-martinvach-sitemap -- php artisan app:sync-drive-to-local "KB/MyProjects/Larapi/imports" "app/imports" -- php artisan app:google-drive-synchronize "KB/MyProjects/Larapi/imports" "app/imports" -- php artisan app:google-drive-download-files "KB/MyProjects/ivnbg.com/content/posts/place" "app/imports/projects/ivnbg/posts/place" -- php artisan app:google-drive-synchronize "albums" "storage/albums" - -``` --c, --controller Create a new controller for the model --f, --factory Create a new factory for the model ---force Create the class even if the model already exists --m, --migration Create a new migration file for the model --s, --seed Create a new seeder file for the model --p, --pivot Indicates if the generated model should be a custom intermediate table model --r, --resource Indicates if the generated controller should be a resource controller -For More Help -php artisan make:model Todo -help -``` - -```bash -php artisan cache:clear -php artisan view:clear -php artisan config:clear -php artisan route:clear -composer dump-autoload -``` - -``` - base_path(); // '/var/www/mysite' - app_path(); // '/var/www/mysite/app' - storage_path(); // '/var/www/mysite/storage' -``` +# Laravel multi domain app \ No newline at end of file diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php deleted file mode 100644 index aebff5e..0000000 --- a/app/Actions/Fortify/CreateNewUser.php +++ /dev/null @@ -1,39 +0,0 @@ - $input - */ - public function create(array $input): User - { - Validator::make($input, [ - 'name' => ['required', 'string', 'max:255'], - 'email' => [ - 'required', - 'string', - 'email', - 'max:255', - Rule::unique(User::class), - ], - 'password' => $this->passwordRules(), - ])->validate(); - - return User::create([ - 'name' => $input['name'], - 'email' => $input['email'], - 'password' => $input['password'], - ]); - } -} diff --git a/app/Actions/Fortify/PasswordValidationRules.php b/app/Actions/Fortify/PasswordValidationRules.php deleted file mode 100644 index 76b19d3..0000000 --- a/app/Actions/Fortify/PasswordValidationRules.php +++ /dev/null @@ -1,18 +0,0 @@ -|string> - */ - protected function passwordRules(): array - { - return ['required', 'string', Password::default(), 'confirmed']; - } -} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php deleted file mode 100644 index 688d62f..0000000 --- a/app/Actions/Fortify/ResetUserPassword.php +++ /dev/null @@ -1,28 +0,0 @@ - $input - */ - public function reset(User $user, array $input): void - { - Validator::make($input, [ - 'password' => $this->passwordRules(), - ])->validate(); - - $user->forceFill([ - 'password' => $input['password'], - ])->save(); - } -} diff --git a/app/Console/Commands/DownloadGitHubPublishFiles.php b/app/Console/Commands/DownloadGitHubPublishFiles.php new file mode 100644 index 0000000..de9b363 --- /dev/null +++ b/app/Console/Commands/DownloadGitHubPublishFiles.php @@ -0,0 +1,60 @@ +info('Fetching file list from GitHub…'); + + try { + $paths = DownloadPublishFiles::getFileList(); + } catch (\Throwable $e) { + $this->error($e->getMessage()); + return self::FAILURE; + } + + $this->line(sprintf('Found %d files.', count($paths))); + + if ($this->option('dry-run')) { + foreach ($paths as $path) { + $this->line(" $path"); + } + return self::SUCCESS; + } + + $this->info('Downloading…'); + $bar = $this->output->createProgressBar(count($paths)); + $bar->start(); + + $failed = []; + + DownloadPublishFiles::downloadFiles($paths, function (string $path, $result) use ($bar, &$failed) { + if ($result !== true) { + $failed[$path] = $result; + } + $bar->advance(); + }); + + $bar->finish(); + $this->newLine(2); + + if (!empty($failed)) { + $this->warn(count($failed) . ' file(s) failed:'); + foreach ($failed as $path => $error) { + $this->line(" ✘ $path — $error"); + } + return self::FAILURE; + } + + $this->info('✔ All files saved to storage/app/imports.'); + return self::SUCCESS; + } +} \ No newline at end of file diff --git a/app/Console/Commands/GenerateAlbum.php b/app/Console/Commands/GenerateAlbum.php new file mode 100644 index 0000000..691bacf --- /dev/null +++ b/app/Console/Commands/GenerateAlbum.php @@ -0,0 +1,57 @@ +option('url'); + if(empty($url)) { + $url = config('myapp.album.url'); + } + $this->info('Generating albums: '); + try { + $service = new AlbumGeneratorService($url); + $service->handle(); + + $errors = $service->getErrors(); + foreach ($errors as $message) { + $this->error($message); + Log::error($message); + } + + $success = $service->getSuccess(); + foreach ($success as $message) { + $this->info($message); + Log::info($message); + } + + }catch (Exception $e) { + $this->error($e->getMessage()); + Log::error($e->getMessage()); + } + } +} diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php new file mode 100644 index 0000000..c07d13f --- /dev/null +++ b/app/Console/Commands/GenerateSitemap.php @@ -0,0 +1,60 @@ +argument('project'); + $path = $this->option('path') ?? public_path(); + + logger()->info("Starting sitemap generation for {$project} | {$path}/sitemap.xml"); + + $this->info("🗺 Starting sitemap generation for {$project} | {$path}/sitemap.xml"); + $this->newLine(); + + try { + $this->bootstrap($project); + + $pages = $this->fetchContents(ContentContentType::Page); + $metaPages = $this->fetchContents(ContentContentType::Meta); + $articles = $this->fetchContents(ContentContentType::Article); + $places = $this->fetchContents(ContentContentType::Place); + $tutorials = $this->fetchContents(ContentContentType::Tutorial); + $guides = $this->fetchContents(ContentContentType::Guide); + $aiprompts = $this->fetchContents(ContentContentType::Aiprompt); + + $this->logFetchedCounts(articles: $articles, pages: $pages, metaPages: $metaPages, places: $places, + tutorials: $tutorials, guides: $guides, aiprompts: $aiprompts); + $this->initProgressBar(articles: $articles, pages: $pages, metaPages: $metaPages, places: $places, + tutorials: $tutorials, guides: $guides, aiprompts: $aiprompts); + + $this->addContents($pages, 'pages'); + $this->addContents($metaPages); + $this->addContents($articles, 'blog'); + $this->addContents($places, 'place'); + $this->addContents($tutorials, 'tutorial'); + $this->addContents($guides, 'guide'); + $this->addContents($aiprompts, 'aiprompt'); + + $this->writeSitemap($this->option('path')); + $this->printSuccessSummary(articles: $articles, pages: $pages, metaPages: $metaPages, places: $places, + tutorials: $tutorials, guides: $guides, aiprompts: $aiprompts); + + return self::SUCCESS; + + } catch (Throwable $e) { + $this->printError($e); + report($e); + + return self::FAILURE; + } + } +} \ No newline at end of file diff --git a/app/Console/Commands/ImportProjectContent.php b/app/Console/Commands/ImportProjectContent.php new file mode 100644 index 0000000..2b9fa3e --- /dev/null +++ b/app/Console/Commands/ImportProjectContent.php @@ -0,0 +1,57 @@ +info('Starting project content import...'); + $this->newLine(); + + // Run the import logic + $service->handle(); + + // Output Successes + if (count($service->getSuccess()) > 0) { + $this->info('Successfully imported:'); + foreach ($service->getSuccess() as $message) { + $this->line("- OK: {$message}"); + } + } + + $this->newLine(); + + // Output Errors + if (count($service->getErrors()) > 0) { + $this->error('Errors encountered during import:'); + foreach ($service->getErrors() as $error) { + $this->line("- FAIL: {$error}"); + } + } + + $this->newLine(); + $this->info('Import process completed.'); + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/app/Console/Commands/SitemapBase.php b/app/Console/Commands/SitemapBase.php new file mode 100644 index 0000000..3d8ab02 --- /dev/null +++ b/app/Console/Commands/SitemapBase.php @@ -0,0 +1,142 @@ +baseUrl = $appProject->getUrl(); + $this->domainManager->setSlug($appProject->value); + $this->projectId = $this->domainManager->getProjectId(); + + config(['app.project_id' => $this->projectId]); + + $this->sitemap = Sitemap::create(); + } + + protected function initProgressBar(Collection ...$collections): void + { + $staticCount = 3; // home + about + contact + $total = $staticCount + array_sum(array_map(fn(Collection $c) => $c->count(), $collections)); + + $this->bar = $this->output->createProgressBar($total); + $this->bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% — %message%'); + $this->bar->setMessage('Initialising...'); + $this->bar->start(); + } + + protected function fetchContents(ContentContentType $contentType): Collection + { + return Content::withoutGlobalScopes() + ->where('project_id', $this->projectId) + ->publishedByType($contentType) + ->get(); + } + + protected function addContents(Collection $contents, string $routeName = null): void + { + foreach ($contents as $content) { + $urlPath = $routeName ? "{$routeName}/{$content->slug}" : $content->slug; + $this->bar->setMessage("Adding content: {$urlPath}"); + $this->sitemap->add( + Url::create("{$this->baseUrl}/{$urlPath}") + ->setLastModificationDate(Carbon::yesterday()) + ); + $this->bar->advance(); + } + } + + protected function writeSitemap(string $publicPath = null): void + { + $path = $publicPath ?? public_path('sitemap.xml'); + + if ($publicPath) { + $path = rtrim($publicPath, '/') . '/sitemap.xml'; + } + + $this->bar->setMessage("Writing sitemap.xml to {$path}..."); + $this->sitemap->writeToFile($path); + $this->bar->finish(); + } + + // ── Output helpers ─────────────────────────────────────────────────────── + + protected function logFetchedCounts(Collection ...$collections): void + { + $parts = array_map( + fn(Collection $collection, string $label) => "{$collection->count()} {$label}", + $collections, + array_keys($collections) + ); + + $this->line(' Found ' . implode(' and ', $parts) . '.'); + $this->newLine(); + } + + protected function logFetchedCountsOld(Collection $categories, Collection $articles): void + { + $this->line(" Found {$categories->count()} categories and {$articles->count()} articles."); + $this->newLine(); + } + + protected function printSuccessSummary(Collection ...$collections): void + { + $total = 3 + array_sum(array_map(fn(Collection $c) => $c->count(), $collections)); + + $this->newLine(2); + $this->info('✅ Sitemap generated successfully → public/sitemap.xml'); + $this->line(" Total URLs written: {$total}"); + $this->newLine(); + + logger()->info('Sitemap generated successfully', [ + 'total_urls' => $total + ]); + } + + protected function printError(Throwable $e): void + { + $this->newLine(2); + $this->error('❌ Sitemap generation failed!'); + $this->newLine(); + $this->line(" Error: {$e->getMessage()}"); + $this->line(" File: {$e->getFile()} (line {$e->getLine()})"); + $this->newLine(); + + logger()->error('Sitemap generation failed', [ + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + // 'trace' => $e->getTraceAsString(), + ]); + } +} \ No newline at end of file diff --git a/app/Data/CategoryData.php b/app/Data/CategoryData.php new file mode 100644 index 0000000..511c473 --- /dev/null +++ b/app/Data/CategoryData.php @@ -0,0 +1,46 @@ + 'https://www.ivnbg.com', + self::MartinVach => 'https://www.martinvach.com', + self::MyPrompties => 'https://www.myprompties.com', + self::Vades => 'https://www.vades.dev', + self::Aitomatix => 'https://www.aitomatix.com', + self::AitomatixCz => 'https://www.aitomatix.cz', + self::LaravelCore => 'http://laravel-core.test', + }; + } +} \ No newline at end of file diff --git a/app/Enums/ContentContentType.php b/app/Enums/ContentContentType.php new file mode 100644 index 0000000..54985c7 --- /dev/null +++ b/app/Enums/ContentContentType.php @@ -0,0 +1,31 @@ + 'English', + self::ES => 'Spanish', + self::FR => 'French', + self::DE => 'German', + }; + } +} \ No newline at end of file diff --git a/app/Enums/UserRole.php b/app/Enums/UserRole.php new file mode 100644 index 0000000..6bf35cd --- /dev/null +++ b/app/Enums/UserRole.php @@ -0,0 +1,12 @@ + $query->meta(basename($request->path())), + 'contents' => $query->filtered(), + ]); + } + + + /** + * Display the specified resource. + */ + public function show(string $slug, AlbumQuery $album): View + { + + $query = new ContentQuery(ContentContentType::Article); + $content = $query->findBySlug($slug, ['user']); + + $next = $content->nextPublishedByType(ContentContentType::Article); + $previous = $content->previousPublishedByType(ContentContentType::Article); + + $viewMode = $content['viewMode'] ?? 'default'; + + return view('article.show-' .$viewMode, [ + 'page' => $content, + 'nextContent' => $next ? route('articleShow', ['slug' => $next->slug]) : null, + 'previousContent' => $previous ? route('articleShow', ['slug' => $previous->slug]) : null, + 'postImages' => $album->postImages($content), + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Web/Default/HomeController.php b/app/Http/Controllers/Web/Default/HomeController.php new file mode 100644 index 0000000..e720c1a --- /dev/null +++ b/app/Http/Controllers/Web/Default/HomeController.php @@ -0,0 +1,30 @@ + (new ContentQuery)->meta('home'), + 'placesFeatured' => $places->featured(take: 6), + 'places' => $places->latest(take: 12, excludeFeatured: true), + 'articles' => $articles->latest(take: 6), + 'images' => $album->homeImages(take: 6), + ]); + } +} diff --git a/app/Http/Controllers/Web/Default/PageController.php b/app/Http/Controllers/Web/Default/PageController.php new file mode 100644 index 0000000..4ecb6b6 --- /dev/null +++ b/app/Http/Controllers/Web/Default/PageController.php @@ -0,0 +1,22 @@ + $query->findBySlug($slug), + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Web/Default/PhotoGalleryController.php b/app/Http/Controllers/Web/Default/PhotoGalleryController.php new file mode 100644 index 0000000..3ab5c32 --- /dev/null +++ b/app/Http/Controllers/Web/Default/PhotoGalleryController.php @@ -0,0 +1,44 @@ +path()); + + return view('photo-gallery.index', [ + 'page' => (new ContentQuery)->meta($slug), + 'images' => $album->events(), + ]); + } + + public function show(string $slug,DomainManagerService $domainManager,AlbumQuery $album): View + { + $meta = (new ContentQuery)->meta('photo-gallery'); + $event = $album->eventByDirectory($slug); + debug($event); + + if(!empty($event->title)){ + $meta->title = $meta->title .' - ' .$event->title; + } + + return view('photo-gallery.show', [ + 'page' => $meta , + 'images' => $album->imagesByDirectory($slug), + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Web/Default/PlaceController.php b/app/Http/Controllers/Web/Default/PlaceController.php new file mode 100644 index 0000000..93e3ea9 --- /dev/null +++ b/app/Http/Controllers/Web/Default/PlaceController.php @@ -0,0 +1,54 @@ + $query->meta(basename($request->path())), + 'contents' => $query->filtered(), + ] + ); + } + + + /** + * Display the specified resource. + */ + public function show(string $slug, AlbumQuery $album): View + { + $query = new ContentQuery(ContentContentType::Place); + $content = $query->findBySlug($slug); + + $next = $content->nextPublishedByType(ContentContentType::Place); + $previous = $content->previousPublishedByType(ContentContentType::Place); + + $categorySlugs = $content->categories->pluck('slug')->toArray(); + return view('place.show', [ + 'page' => $content, + 'nextContent' => $next ? route('placeShow', ['slug' => $next->slug]) : null, + 'previousContent' => $previous ? route('placeShow', ['slug' => $previous->slug]) : null, + 'images' => $album->imagesByDirectory($slug), + 'highlights' => $query->byParentId(parentId:$content->id,take: 6, random: true), + 'related' => $query->setFilter('category',$categorySlugs)->filtered(), + ]); + } +} diff --git a/app/Http/Controllers/Web/Default/TagController.php b/app/Http/Controllers/Web/Default/TagController.php new file mode 100644 index 0000000..502c514 --- /dev/null +++ b/app/Http/Controllers/Web/Default/TagController.php @@ -0,0 +1,24 @@ +path()); + $query = new TagQuery($contentType); + return view('tag.index', [ + 'page' => (new ContentQuery)->meta('tags-'. $contentType), + 'tags' => $query->all(), + 'routeName' => $contentType . 'Index', + ]); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/AbortWithNotFound.php b/app/Http/Middleware/AbortWithNotFound.php new file mode 100644 index 0000000..97200a5 --- /dev/null +++ b/app/Http/Middleware/AbortWithNotFound.php @@ -0,0 +1,20 @@ +logout(); - - Session::invalidate(); - Session::regenerateToken(); - - return redirect('/'); - } -} diff --git a/app/Models/Category.php b/app/Models/Category.php index 6a27116..b37fd51 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -2,10 +2,17 @@ namespace App\Models; +use App\Enums\ContentContentType; +use App\Enums\ContentStatus; +use App\Enums\ContentVisibility; +use App\Enums\Language; +use App\Traits\FilterByProject; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +use Spatie\Sluggable\SlugOptions; /** * App\Models\Category @@ -34,6 +41,7 @@ class Category extends Model { use HasFactory; use SoftDeletes; + use FilterByProject; /** * The attributes that are mass assignable. @@ -59,6 +67,10 @@ class Category extends Model * @var array */ protected $casts = [ + 'status' => ContentStatus::class, + 'content_type' => ContentContentType::class, + 'visibility' => ContentVisibility::class, + 'lang' => Language::class, 'metadata' => 'array', 'position' => 'integer', ]; @@ -78,4 +90,24 @@ public function parent(): BelongsTo { return $this->belongsTo(Category::class, 'parent_id'); } -} + + public function contents() + { + return $this->belongsToMany(Content::class); + } + + public function scopePublishedByType(Builder $query, null|string|ContentContentType $contentType = ContentContentType::Article->value): void + { + $value = $contentType instanceof ContentContentType ? $contentType->value : $contentType; + + $query->where('status', ContentStatus::Published->value) + ->where('content_type', $value) ; + } + public function getSlugOptions() : SlugOptions + { + return SlugOptions::create() + ->generateSlugsFrom('slug') + ->saveSlugsTo('slug') + ->doNotGenerateSlugsOnUpdate(); + } +} \ No newline at end of file diff --git a/app/Models/Content.php b/app/Models/Content.php index 4b663a8..1b4551b 100644 --- a/app/Models/Content.php +++ b/app/Models/Content.php @@ -2,10 +2,21 @@ namespace App\Models; +use App\Enums\ContentContentType; +use App\Enums\ContentStatus; +use App\Enums\ContentVisibility; +use App\Enums\Language; +use App\Traits\FilterByProject; +use App\Traits\HasDynamicContent; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Http\Request; +use Illuminate\Support\Str; +use Spatie\Sluggable\SlugOptions; /** * App\Models\Content @@ -42,7 +53,8 @@ class Content extends Model { use HasFactory; use SoftDeletes; - + use FilterByProject; + use HasDynamicContent; /** * The attributes that are mass assignable. * @@ -50,6 +62,10 @@ class Content extends Model */ protected $fillable = [ 'uuid', + 'project_id', + 'user_id', + 'author_id', + 'parent_id', 'content_type', 'status', 'visibility', @@ -63,6 +79,7 @@ class Content extends Model 'position', 'is_featured', 'published_at', + ]; /** @@ -71,6 +88,10 @@ class Content extends Model * @var array */ protected $casts = [ + 'status' => ContentStatus::class, + 'content_type' => ContentContentType::class, + 'visibility' => ContentVisibility::class, + 'lang' => Language::class, 'metadata' => 'array', 'is_featured' => 'boolean', 'position' => 'integer', @@ -108,4 +129,156 @@ public function parent(): BelongsTo { return $this->belongsTo(Content::class, 'parent_id'); } + + public function categories() + { + return $this->belongsToMany(Category::class, 'category_content'); + } + + public function tags() + { + return $this->belongsToMany(Tag::class,'content_tag'); + } + + public function getSlugOptions() : SlugOptions + { + return SlugOptions::create() + ->generateSlugsFrom('slug') + ->saveSlugsTo('slug') + ->doNotGenerateSlugsOnUpdate(); + } + + /** + * Get rendered content. + */ + protected function renderedContent(): Attribute + { + return Attribute::make( + get: fn () => Str::of($this->content)->markdown(), + ); + } + + /** + * Get the cover image URL from metadata. + */ + protected function coverImageUrl(): Attribute + { + return Attribute::make( + get: fn () => !empty($this->metadata['coverImage']) ? config('myapp.image.domain').'/'.$this->metadata['coverImage'] : null, + ); + } + /** + * Get the featured image URL from metadata. + */ + protected function featuredImageUrl(): Attribute + { + return Attribute::make( + get: fn () => !empty($this->metadata['featuredImage']) ? config('myapp.image.domain').'/'. $this->metadata['featuredImage'] : null, + ); + } + + /** + * Get the featured image URL from metadata. + */ + protected function livewireWidget(): Attribute + { + return Attribute::make( + get: fn () => $this->metadata['livewireWidget'] ?? null, + ); + } + + /** + * Get the address from metadata. + */ + protected function address(): Attribute + { + return Attribute::make( + get: fn () => $this->metadata['address'] ?? null, + ); + } + + /** + * Get the Google Map Embed URL from metadata. + */ + protected function googleMapEmbedUrl(): Attribute + { + return Attribute::make( + get: fn () => $this->metadata['googleMapEmbedUrl'] ?? null, + ); + } + + /** + * Get the Meta Title from metadata. + */ + protected function metaTitle(): Attribute + { + return Attribute::make( + get: fn () => $this->metadata['metaTitle'] ?? null, + ); + } + + /** + * Get the Meta Description from metadata. + */ + protected function metaDescription(): Attribute + { + return Attribute::make( + get: fn () => $this->metadata['metaDescription'] ?? null, + ); + } + + public function scopePublished(Builder $query): void + { + $query->where('status', ContentStatus::Published->value); + } + + public function scopePublishedByType(Builder $query, null|string|ContentContentType $contentType = ContentContentType::Article->value): + void + { + $value = $contentType instanceof ContentContentType ? $contentType->value : $contentType; + $query->where('status', ContentStatus::Published->value) + ->where('content_type', $value) + ; + } + public function scopeIsFeatured(Builder $query): void + { + $query->where('is_featured', 1); + } + + public function scopeNotFeatured(Builder $query): void + { + $query->where('is_featured', 0); + } + + public function scopeFilterByCategory(Builder $query, array|string $value): void + { + $query->whereHas('categories', fn($q) => + $q->whereIn('slug', (array) $value) + ); + } + + public function scopeFilterByTag(Builder $query, array|string $value): void + { + $query->whereHas('tags', fn($q) => + $q->whereIn('name', (array) $value) + ); + } + + public function nextPublishedByType(string|ContentContentType $contentType =ContentContentType::Article->value) + { + $value = $contentType instanceof ContentContentType ? $contentType->value : $contentType; + return $this->publishedByType( $value) + ->where('id', '>', $this->id) + ->orderBy('id') + ->first(); + } + + public function previousPublishedByType(string|ContentContentType $contentType = ContentContentType::Article->value) + { + $value = $contentType instanceof ContentContentType ? $contentType->value : $contentType; + return $this->publishedByType($contentType) + ->where('id', '<', $this->id) + ->orderByDesc('id') + ->first(); + } } diff --git a/app/Models/ContentAnalytic.php b/app/Models/ContentAnalytic.php new file mode 100644 index 0000000..176967a --- /dev/null +++ b/app/Models/ContentAnalytic.php @@ -0,0 +1,53 @@ + + */ + protected $fillable = [ + 'views', + 'unique_views', + 'downloads', + 'last_viewed_at', + 'metadata', + ]; + + /** + * @var array + */ + protected $casts = [ + 'views' => 'int', + 'unique_views' => 'int', + 'downloads' => 'int', + 'last_viewed_at' => 'datetime', + 'metadata' => 'array', + ]; + + /** + * Get the content that owns the analytic. + */ + public function content(): BelongsTo + { + return $this->belongsTo(Content::class); + } +} \ No newline at end of file diff --git a/app/Models/Inquiry.php b/app/Models/Inquiry.php index 074c590..22ba0e5 100644 --- a/app/Models/Inquiry.php +++ b/app/Models/Inquiry.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Traits\FilterByProject; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -32,6 +34,8 @@ class Inquiry extends Model { use SoftDeletes; + use FilterByProject; + use HasFactory; /** * The attributes that are mass assignable. @@ -39,6 +43,7 @@ class Inquiry extends Model * @var array */ protected $fillable = [ + 'project_id', 'is_read', 'is_spam', 'is_archived', @@ -83,5 +88,4 @@ public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(User::class); } -} - +} \ No newline at end of file diff --git a/app/Models/Project.php b/app/Models/Project.php index 0947dc6..b6198d3 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -65,4 +65,4 @@ public function users() { return $this->hasMany(User::class); } -} +} \ No newline at end of file diff --git a/app/Models/Tag.php b/app/Models/Tag.php index a390a5f..60e9a33 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -2,6 +2,12 @@ namespace App\Models; +use App\Enums\ContentContentType; +use App\Enums\ContentStatus; +use App\Enums\Language; +use App\Traits\FilterByProject; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -25,6 +31,8 @@ class Tag extends Model { use SoftDeletes; + use FilterByProject; + use HasFactory; /** * The attributes that are mass assignable. @@ -33,11 +41,13 @@ class Tag extends Model */ protected $fillable = [ 'uuid', + 'content_type', 'is_published', 'tag_type', 'lang', 'views_count', 'name', + 'project_id', ]; /** @@ -46,6 +56,7 @@ class Tag extends Model * @var array */ protected $casts = [ + 'lang' => Language::class, 'is_published' => 'bool', 'views_count' => 'int', 'created_at' => 'datetime', @@ -60,5 +71,15 @@ public function project(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Project::class); } -} + public function contents() + { + return $this->belongsToMany(Content::class); + } + + public function scopeByContentType(Builder $query, null|string|ContentContentType $contentType =ContentContentType::Article->value): void + { + $value = $contentType instanceof ContentContentType ? $contentType->value : $contentType; + $query->where('content_type',$value); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 214bea4..a00d8b9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,28 +3,36 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; -use Laravel\Fortify\TwoFactorAuthenticatable; class User extends Authenticatable { - /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, TwoFactorAuthenticatable; + /** @use HasFactory */ + use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var list */ + /** + * The attributes that are mass assignable. + * + * @var array + */ protected $fillable = [ 'name', 'email', 'password', + 'project_id', // Ensure this is here + 'role', // Ensure this is here + 'account_type', // Ensure this is here + 'metadata', // Ensure this is here ]; - /** * The attributes that should be hidden for serialization. * @@ -47,6 +55,7 @@ protected function casts(): array return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'metadata' => 'array', ]; } @@ -61,4 +70,4 @@ public function initials(): string ->map(fn ($word) => Str::substr($word, 0, 1)) ->implode(''); } -} +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..2b7f969 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use App\View\Composers\CategoryComposer; +use App\View\Composers\TagComposer; +use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -19,6 +22,7 @@ public function register(): void */ public function boot(): void { - // + View::composer([ 'components.ui.my-categories-dropdown.index'], CategoryComposer::class); + //View::composer('*', TagComposer::class); } -} +} \ No newline at end of file diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php deleted file mode 100644 index 44e57aa..0000000 --- a/app/Providers/FortifyServiceProvider.php +++ /dev/null @@ -1,72 +0,0 @@ -configureActions(); - $this->configureViews(); - $this->configureRateLimiting(); - } - - /** - * Configure Fortify actions. - */ - private function configureActions(): void - { - Fortify::resetUserPasswordsUsing(ResetUserPassword::class); - Fortify::createUsersUsing(CreateNewUser::class); - } - - /** - * Configure Fortify views. - */ - private function configureViews(): void - { - Fortify::loginView(fn () => view('livewire.auth.login')); - Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email')); - Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge')); - Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password')); - Fortify::registerView(fn () => view('livewire.auth.register')); - Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password')); - Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password')); - } - - /** - * Configure rate limiting. - */ - private function configureRateLimiting(): void - { - RateLimiter::for('two-factor', function (Request $request) { - return Limit::perMinute(5)->by($request->session()->get('login.id')); - }); - - RateLimiter::for('login', function (Request $request) { - $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip()); - - return Limit::perMinute(5)->by($throttleKey); - }); - } -} diff --git a/app/Providers/MultiDomainServiceProvider.php b/app/Providers/MultiDomainServiceProvider.php new file mode 100644 index 0000000..e83e495 --- /dev/null +++ b/app/Providers/MultiDomainServiceProvider.php @@ -0,0 +1,157 @@ +registerDomainManagerService(); + } + + public function boot(DomainManagerService $domainManager): void + { + $this->initializePaths($domainManager); + + $this->configureViewCascade(); + $this->configureViteBuildDirectory(); + $this->shareGlobalCssPath(); + //$this->shareGlobalNavigation(); + + // Routing is disabled due to double-loading issues + // $this->configureRouting(); + } + + /** + * Register the DomainManagerService as a singleton + */ + private function registerDomainManagerService(): void + { + $this->app->singleton(DomainManagerService::class, function ($app) { + return new DomainManagerService($app->request); + }); + } + + /** + * Initialize common paths used throughout the provider + */ + private function initializePaths(DomainManagerService $domainManager): void + { + $this->slug = $domainManager->getSlug(); + $this->siteViewPath = resource_path("views/components/{$this->slug}"); + $this->defaultViewPath = resource_path("views/components/default"); + } + + /** + * Configure view cascade: site-specific → default → standard resources/views + */ + private function configureViewCascade(): void + { + // Add site-specific views first (highest priority) + if (is_dir($this->siteViewPath)) { + View::share('globalViewPath', $this->siteViewPath); + View::getFinder()->prependLocation($this->siteViewPath); + } + + // Add default views as fallback + if (is_dir($this->defaultViewPath)) { + View::share('globalViewPath', $this->defaultViewPath); + View::getFinder()->addLocation($this->defaultViewPath); + } + } + + /** + * Configure Vite to use the appropriate build directory + */ + private function configureViteBuildDirectory(): void + { + Vite::useBuildDirectory('build'); + } + + /** + * Share the appropriate CSS path with all views + */ + private function shareGlobalCssPath(): void + { + $cssPath = $this->resolveCssPath(); + View::share('globalCssPath', $cssPath); + } + + /** + * Determine which CSS file to use (site-specific or default) + */ + private function resolveCssPath(): string + { + $customCssPath = "resources/css/{$this->slug}/app.css"; + $defaultCssPath = "resources/css/default/app.css"; + + return file_exists(resource_path("css/{$this->slug}/app.css")) + ? $customCssPath + : $defaultCssPath; + } + + /** + * TODO: deprecated + * Share navigation data with all views + */ + private function shareGlobalNavigation(): void + { + $navigation = $this->loadNavigationData(); + View::share('globalNav', $navigation); + } + + /** + * TODO: deprecated + * Load navigation data from site-specific or default location + */ + private function loadNavigationData(): array + { + $siteNavPath = $this->siteViewPath . '/data/nav.php'; + $defaultNavPath = $this->defaultViewPath . '/data/nav.php'; + + $navPath = file_exists($siteNavPath) ? $siteNavPath : $defaultNavPath; + + return require_once $navPath; + } + + /** + * Configure domain-specific routing + * + * TODO: Currently disabled - routes are loaded twice causing conflicts + * Need to implement proper route caching or registration guard + */ + private function configureRouting(): void + { + // Prevent double-loading of routes + if (!app()->has('routes_loaded_by_multidomain')) { + $this->registerRoutes(); + app()->instance('routes_loaded_by_multidomain', true); + } + } + + /** + * Register routes from domain-specific file or fallback to web.php + */ + private function registerRoutes(): void + { + $routeFile = base_path("routes/{$this->slug}.php"); + + if (file_exists($routeFile)) { + ds("Loading domain route file: {$routeFile}"); + Route::middleware('web')->group($routeFile); + } else { + ds("Loading fallback route file: routes/web.php"); + Route::middleware('web')->group(base_path('routes/web.php')); + } + } +} \ No newline at end of file diff --git a/app/Queries/AlbumQuery.php b/app/Queries/AlbumQuery.php new file mode 100644 index 0000000..2787b35 --- /dev/null +++ b/app/Queries/AlbumQuery.php @@ -0,0 +1,61 @@ +domainManager->getSlug())) + ->shuffle() + ->when($take > 0, fn($q) => $q->take($take)) + ->values() + ->toArray() + ; + } + + public function imagesByDirectory(string $directory, ?int $take = null,): array + { + return collect(AlbumService::getImages($this->domainManager->getSlug())) + ->where('directory', $directory) + ->when($take > 0, fn($q) => $q->take($take)) + ->values() + ->toArray();; + } + + public function eventByDirectory(string $directory): \stdClass|null + { + return collect(AlbumService::getEvents($this->domainManager->getSlug())) + ->firstWhere('directory', $directory); + } + + public function events(?int $take = null): array + { + return collect(AlbumService::getEvents($this->domainManager->getSlug())) + ->when($take > 0, fn($q) => $q->take($take)) + ->values() + ->toArray();; + } + + + public function postImages(Content $content): ?array + { + if (blank($content['eventDirectory'])) { + return null; + } + return null; + + /* return collect(AlbumService::fetchPostImages()) + ->where('directory', $content['eventDirectory']) + ->values() + ->toArray();*/ + } +} \ No newline at end of file diff --git a/app/Queries/CategoryQuery.php b/app/Queries/CategoryQuery.php new file mode 100644 index 0000000..dd65b3f --- /dev/null +++ b/app/Queries/CategoryQuery.php @@ -0,0 +1,36 @@ +remember( + cacheName: 'categories', + callback: fn() => Category::publishedByType($this->contentType) + ->whereHas('contents', function (Builder $subQuery) { + $subQuery->withoutGlobalScope('project_scope'); + }) + ->withCount(['contents' => function (Builder $subQuery) { + $subQuery->withoutGlobalScope('project_scope'); + }]) + ->get(), + contentType: $this->contentType, + ); + } +} \ No newline at end of file diff --git a/app/Queries/ContentQuery.php b/app/Queries/ContentQuery.php new file mode 100644 index 0000000..aaf19ca --- /dev/null +++ b/app/Queries/ContentQuery.php @@ -0,0 +1,108 @@ +filters[$key] = $value; + return $this; + } + + public function meta(string $slug): Content + { + return Content::publishedByType(ContentContentType::Meta) + ->where('slug', $slug) + ->firstOrFail() + ; + } + + public function findBySlug(string $slug, array $with = []): Content + { + return Content::publishedByType($this->contentType) + ->where('slug', $slug) + ->when(!empty($with), fn($q) => $q->with($with)) + ->firstOrFail() + ; + } + + public function featured(?int $take = null, bool $random = false,): Collection + { + return Content::publishedByType($this->contentType) + ->isFeatured() + ->when($random, fn($q) => $q->inRandomOrder()) + ->latest() + ->when($take > 0, fn($q) => $q->take($take)) + ->get() + ; + } + + public function latest(?int $take = null, bool $random = false, bool $excludeFeatured = false): Collection + { + return Content::publishedByType($this->contentType) + ->when($excludeFeatured, fn($q) => $q->notFeatured()) + ->when($random, fn($q) => $q->inRandomOrder()) + ->latest() + ->when($take > 0, fn($q) => $q->take($take)) + ->get() + ; + } + + public function byParentId(int $parentId, ?int $take = null, bool $random = false): Collection + { + return Content::publishedByType($this->contentType) + ->where('parent_id', $parentId) + ->when($random, fn($q) => $q->inRandomOrder()) + ->latest() + ->when($take > 0, fn($q) => $q->take($take)) + ->get() + ; + } + + public function filtered(int $perPage = 20): LengthAwarePaginator|Collection + { + $query = QueryBuilder::for(Content::publishedByType($this->contentType)) + ->allowedFilters( + AllowedFilter::scope('category', 'filterByCategory'), + AllowedFilter::scope('tag', 'filterByTag'), + ) + ->allowedIncludes('categories', 'tags') + ->with(['categories', 'tags']) + ->when( + isset($this->filters['category']), + fn($q) => $q->filterByCategory( + is_array($this->filters['category']) + ? $this->filters['category'] + : [$this->filters['category']] + ) + ) + ->when( + isset($this->filters['tag']), + fn($q) => $q->filterByTag( + is_array($this->filters['tag']) + ? $this->filters['tag'] + : [$this->filters['tag']] + ) + ) + ->orderBy('created_at', 'desc') + ; + + return $perPage > 0 + ? $query->paginate($perPage) + : $query->get(); + } +} \ No newline at end of file diff --git a/app/Queries/TagQuery.php b/app/Queries/TagQuery.php new file mode 100644 index 0000000..bb34f7a --- /dev/null +++ b/app/Queries/TagQuery.php @@ -0,0 +1,36 @@ +remember( + cacheName: 'tags', + callback: fn() => Tag::byContentType($this->contentType) + ->whereHas('contents', function (Builder $subQuery) { + $subQuery->withoutGlobalScope('project_scope'); + }) + ->withCount(['contents' => function (Builder $subQuery) { + $subQuery->withoutGlobalScope('project_scope'); + }]) + ->get(), + contentType: $this->contentType, + ); + } +} \ No newline at end of file diff --git a/app/Services/Album/AlbumDataResource.php b/app/Services/Album/AlbumDataResource.php new file mode 100644 index 0000000..8f0a3fe --- /dev/null +++ b/app/Services/Album/AlbumDataResource.php @@ -0,0 +1,13 @@ + 0, + ]; +} \ No newline at end of file diff --git a/app/Services/Album/AlbumGeneratorService.php b/app/Services/Album/AlbumGeneratorService.php new file mode 100644 index 0000000..78960db --- /dev/null +++ b/app/Services/Album/AlbumGeneratorService.php @@ -0,0 +1,376 @@ +errors; + } + + private array $success = []; + + public function getSuccess(): array + { + return $this->success; + } + + + public function __construct(string $url) + { + $this->sourceDir = config('myapp.album.dir.source'); + $this->targetDir = config('myapp.album.dir.target'); + $this->url = $url; + $this->albums = new AlbumDataResource(); + + } + + /** + * @throws Exception + */ + public function handle(): void + { + + $this->readAlbumDir($this->sourceDir); + $this->readEventDir($this->sourceDir); + + + } + + private function readAlbumDir(string $sourceDir): void + { + $directories = array_filter(glob($sourceDir . '/*'), 'is_dir'); + if (empty($directories)) { + throw new Exception('No album directories found in: ' . $this->sourceDir); + } + + $directories = array_map('basename', $directories); + $this->albums->data = $this->parseCoverFiles($directories, $sourceDir); + $filePath = config('myapp.album.dir.target') . '/' . config('myapp.album.file.albums'); + $this->storeJsonFile($this->albums, $filePath); + //dd( 'done readAlbumDir'); + } + + private function readEventDir($sourceDir): void + { + $events = []; + + foreach ($this->albums->data as $album) { + $directories = array_filter(glob($sourceDir . '/' . $album['id'] . '/*'), 'is_dir'); + + + if (empty($directories)) { + throw new Exception('No event directories found in: ' . $this->sourceDir); + } + + $directories = array_map('basename', $directories); + $event = new AlbumDataResource(); + $event->data = $this->parseCoverFiles($directories, $sourceDir . '/' . $album['id'], $album['id']); + $this->events[$album['id']] = $event; + } + + foreach ($this->events as $album => $eventList) { + $targetFilePath = config('myapp.album.dir.target') . '/' .$album. '/'.config('myapp.album.file.events'); + $this->storeJsonFile($eventList, $targetFilePath); + $this->readImageDir($this->sourceDir, $eventList->data); + } + + } + + private function readImageDir($sourceDir, $eventList): void + { + foreach ($eventList as $event) { + $srcDir = $sourceDir . '/' . $event['id'] . '/' . config('myapp.album.srcDir'); + $thumbDir = $sourceDir . '/' . $event['id'] . '/' . config('myapp.album.thumbDir'); + if (!is_dir($srcDir)) { + throw new Exception('No image directories found in: ' . $this->sourceDir); + } + $imageFiles = glob($srcDir . '/*.{jpg,gif,png}', GLOB_BRACE); + if (empty($imageFiles)) { + throw new Exception('No images found in: ' . $srcDir); + } + + + foreach ($imageFiles as $imageFile) { + $this->parseImageFile($imageFile, $event, $thumbDir); + } + } + + $targetFilePath = config('myapp.album.dir.target') . '/' . config('myapp.album.file.images'); + + foreach ($this->images as $album => $imageList) { + $targetFilePath = config('myapp.album.dir.target') . '/' .$album. '/'.config('myapp.album.file.images'); + $images = new AlbumDataResource(); + $images->data =$imageList; + //dd($images); + $this->storeJsonFile($images, $targetFilePath); + } + + + + } + + private function parseCoverFiles(array $directories, string $sourceDir, ?string $parentDir = null): array + { + + $items = []; + foreach ($directories as $directory) { + $path = $sourceDir . '/' . $directory; + + $coverPath = $path . '/' . config('myapp.album.cover'); + if (!file_exists($coverPath)) { + $this->errors[] = 'WARNING: No cover found in directory: ' . $path; + continue; + } + + if (file_exists($coverPath) && @getimagesize($coverPath, $imageData)) { + $parentPath = $parentDir ? $parentDir . '/' : ''; + $cover = $this->url . '/' . $parentPath.$directory . '/' . config('myapp.album.cover'); + // $iptc = $this->getIptcData($imageData); // Remove this line + } else { + $this->errors[] = 'WARNING: Invalid cover image found in directory: ' . $path; + continue; + } + + $featuredPath = $path . '/' . config('myapp.album.featured'); + if (!file_exists($featuredPath)) { + $this->errors[] = 'WARNING: No featured found in directory: ' . $path; + } + $featured = null; + if (file_exists($featuredPath) && @getimagesize($featuredPath, $featuredData)) { + $parentPath = $parentDir ? $parentDir . '/' : ''; + $featured = $this->url . '/' . $parentPath.$directory . '/' . config('myapp.album.featured'); + } else { + $this->errors[] = 'WARNING: Invalid featured image found in directory: ' . $path; + } + $options = [ + 'id' => ($parentDir ? $parentDir . '/' : '') . $directory, + 'directory' => $directory, + 'parentId' => $parentDir ?? null, + 'src' => $cover, + 'featured' => $featured, + 'thumbnail' => null, + 'iptc' => $this->getIptcData($coverPath), // Pass image path + ]; + $items[] = $this->parseAlbumImage($options); + + + } + + return $items; + } + + private function parseImageFile(string $imageFile, array $event, string $thumbDir): void + { + $imageData = []; + if (!file_exists($imageFile) && !@getimagesize($imageFile, $imageData)) { + $this->errors[] = 'WARNING: Invalid image found: ' . $imageFile; + } + + + + $fileName = basename($imageFile); + $imagePath = $event['id'] . '/' . config('myapp.album.srcDir') . '/' . $fileName; + $thumUrl = $this->url . '/' . $event['id'] . '/' . config('myapp.album.thumbDir') . '/' . $fileName; + if (!is_dir($thumbDir)) { + mkdir($thumbDir, 0777, true); + } + $thumbPath = $thumbDir . '/' . $fileName; + + if (!file_exists($thumbPath)) { + $this->generateThumbnail($imageFile, $thumbPath, config('myapp.album.thumbWidth')); + } + $options = [ + 'id' => $imagePath, + 'directory' => $event['directory'], + 'parentId' => $event['id'], + 'src' => $this->url . '/' . $imagePath, + 'thumbnail' => $thumUrl, + 'iptc' => $this->getIptcData($imageFile), // Pass image path + //'exif' => @exif_read_data($imageFile, 'ANY_TAG', true), + ]; + + + $this->images[ $event['parentId']][] = $this->parseAlbumImage($options); + + + + + } + private function parseAlbumImage(array $options): array + { + return [ + 'id' => $options['id'], + 'directory' => $options['directory'], + 'parentId' => $options['parentId'], + 'src' => $options['src'], + 'thumbnail' => $options['thumbnail'] ?? null, + 'featured' => $options['featured'] ?? null, + 'title' => $options['iptc']['title'] ?? $options['directory'], + 'createdAt' => new Carbon($options['iptc']['date'] ?? null), + 'description' => $options['iptc']['description'] ?? null, + + 'author' => $options['iptc']['author'] ?? null, + //'tags' => $options['iptc']['tags'] ?? null, + // 'exif' => $options['exif'] ?? null, + + + ]; + + } + + // Refactor getIptcData to accept image path and extract IPTC data inside + private function getIptcData($imagePath): array + { + $return = array('title' => null, 'description' => null, 'author' => null, 'tags' => null, 'date' => null); + if (!file_exists($imagePath)) { + return $return; + } + $info = []; + @getimagesize($imagePath, $info); + if (isset($info['APP13'])) { + $iptc = iptcparse($info['APP13']); + //dump( $imagePath.': '.($iptc ?? 'No IPTC data')); + // Debug: log IPTC fields for troubleshooting + if (!isset($iptc['2#120'][0])) { + /*$this->errors[] = 'DEBUG: No IPTC caption (2#120) found for image: ' . $imagePath . ' IPTC: ' . print_r($iptc, true);*/ + //$this->errors[] = 'DEBUG: No IPTC caption (2#120) found for image: ' . $imagePath ; + } + $return['title'] = isset($iptc['2#005'][0]) ? html_entity_decode($iptc['2#005'][0], ENT_QUOTES | ENT_XML1, 'UTF-8') : null; + // Lightroom "caption" is stored in 2#120, which is mapped to description + if (isset($iptc['2#120'][0])) { + //dump( '2#120: '.$iptc['2#120'][0]); + $return['description'] = html_entity_decode($iptc['2#120'][0], ENT_QUOTES | ENT_XML1, 'UTF-8'); + } elseif (isset($iptc['2#122'][0])) { + dump( '2#122'); + // Fallback to Writer/Editor if caption is missing + $return['description'] = html_entity_decode($iptc['2#122'][0], ENT_QUOTES | ENT_XML1, 'UTF-8'); + } elseif (isset($iptc['2#005'][0])) { + //dump( '2#005: ' . $iptc['2#005'][0]); + // Fallback to Object Name if both are missing + $return['description'] = html_entity_decode($iptc['2#005'][0], ENT_QUOTES | ENT_XML1, 'UTF-8'); + } else { + $return['description'] = null; + } + //dump( 'Description for '.$imagePath.': '.$return['description']); + $return['author'] = isset($iptc['2#080'][0]) ? html_entity_decode($iptc['2#080'][0], ENT_QUOTES | ENT_XML1, 'UTF-8') : null; + $return['tags'] = isset($iptc['2#025']) ? array_map(function($tag) { + return html_entity_decode($tag, ENT_QUOTES | ENT_XML1, 'UTF-8'); + }, $iptc['2#025']) : null; + $return['date'] = isset($iptc['2#062'][0]) ? html_entity_decode($iptc['2#062'][0], ENT_QUOTES | ENT_XML1, 'UTF-8') : null; + } else { + //$this->errors[] = 'DEBUG: No IPTC APP13 found for image: ' . $imagePath; + } + return $return; + } + + private function generateThumbnail(string $imageFile, string $thumbPath, int $thumbWidth = 150): void + { + $imageInfo = getimagesize($imageFile); + if ($imageInfo === false) { + $this->errors[] = 'ERROR: Failed to get image size for: ' . $imageFile; + return; + } + + list($width, $height) = $imageInfo; + $thumbHeight = intval($height * $thumbWidth / $width); + + $thumbnail = imagecreatetruecolor($thumbWidth, $thumbHeight); + + switch ($imageInfo['mime']) { + case 'image/jpeg': + $source = imagecreatefromjpeg($imageFile); + break; + case 'image/png': + $source = imagecreatefrompng($imageFile); + break; + case 'image/gif': + $source = imagecreatefromgif($imageFile); + break; + default: + $this->errors[] = 'ERROR: Unsupported image type for: ' . $imageFile; + return; + } + + imagecopyresampled($thumbnail, $source, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $width, $height); + + switch ($imageInfo['mime']) { + case 'image/jpeg': + imagejpeg($thumbnail, $thumbPath); + break; + case 'image/png': + imagepng($thumbnail, $thumbPath); + break; + case 'image/gif': + imagegif($thumbnail, $thumbPath); + break; + } + + imagedestroy($source); + imagedestroy($thumbnail); + + } + + private function storeJsonFile($dataResource, $targetFilePath): void + { + + + if (count($dataResource->data) < 1) { + $this->errors[] = 'ERROR: No data to store in JSON file: ' . $targetFilePath; + return; + } + + + $dataResource->createdAt = date('Y-m-d H:i:s'); + $dataResource->message = 'OK 200'; + $dataResource->meta['total'] = count($dataResource->data); + + $json = json_encode($dataResource, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES| JSON_UNESCAPED_UNICODE); + if ($json === false) { + $this->errors[] = 'ERROR: Failed to generate JSON from albums array.'; + return; + } + if (@file_put_contents($targetFilePath, $json) === false) { + $this->errors[] = 'ERROR: Failed to save JSON to file: ' . $targetFilePath; + return; + } + $this->success[] = 'SUCCESS: JSON file saved successfully: ' . $targetFilePath; + + + } + + private function readExifData(string $coverPath): array + { + $exifData = @exif_read_data($coverPath, 'ANY_TAG', true); + // Additional information from Lightroom + getimagesize($coverPath, $infos); + if (isset($infos['APP13'])) { + print_r(iptcparse($infos['APP13'])); + } + if ($exifData === false) { + $this->errors[] = 'ERROR: Failed to read EXIF data from image: ' . $coverPath; + return ['title' => null, 'description' => null]; + } + + $title = $exifData['ImageDescription'] ?? null; + $description = $exifData['UserComment'] ?? null; + + return ['title' => $title, 'description' => $description]; + } +} \ No newline at end of file diff --git a/app/Services/Album/AlbumService.php b/app/Services/Album/AlbumService.php new file mode 100644 index 0000000..f7d4d7e --- /dev/null +++ b/app/Services/Album/AlbumService.php @@ -0,0 +1,49 @@ +data ?? []; + } catch (\Throwable $e) { + Log::error("Error reading file {$fullPath}: " . $e->getMessage()); + return []; + } + } + + public static function getAlbums(): array + { + return self::getData(config('myapp.album.file.albums')); + } + + public static function getEvents(string $event): array + { + return self::getData($event .'/'.config('myapp.album.file.events')); + } + + public static function getImages(string $event): array + { + return self::getData($event .'/'.config('myapp.album.file.images')); + } + + +} \ No newline at end of file diff --git a/app/Services/Cache/CacheService.php b/app/Services/Cache/CacheService.php new file mode 100644 index 0000000..a1f731e --- /dev/null +++ b/app/Services/Cache/CacheService.php @@ -0,0 +1,148 @@ +normalizeDomain(); + + if (!empty($contentType)) { + $normalizedType = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $contentType)); + $cacheName .= '_' . trim($normalizedType, '_'); + } + + $hash = substr(md5($normalizedDomain . ':' . $cacheName), 0, 8); + + return "{$normalizedDomain}:{$cacheName}:{$hash}"; + } + + /** + * Store a value in the cache under the generated key. + * + * @param string $cacheName + * @param mixed $value Data to cache + * @param string|null $contentType + * @param int|null $duration TTL in seconds (null = config default) + * @return string The cache key that was used + */ + public function put( + string $cacheName, + mixed $value, + ?string $contentType = null, + ?int $duration = null + ): string { + $key = $this->generateCacheName($cacheName, $contentType); + + Cache::put($key, $value, $duration ?? $this->defaultDuration()); + + return $key; + } + + /** + * Retrieve a cached value by cache name and optional content type. + * + * @param string $cacheName + * @param string|null $contentType + * @param mixed $default Returned when the key is not found + * @return mixed + */ + public function get(string $cacheName, ?string $contentType = null, mixed $default = null): mixed + { + $key = $this->generateCacheName($cacheName, $contentType); + + return Cache::get($key, $default); + } + + /** + * Retrieve a cached value directly by its cache key name. + * + * @param string $cacheName Key previously returned by generateCacheName() or put() + * @param mixed $default + * @return mixed + */ + public function getByName(string $cacheName, mixed $default = null): mixed + { + return Cache::get($cacheName, $default); + } + + /** + * Check whether a cache entry exists. + */ + public function has(string $cacheName, ?string $contentType = null): bool + { + return Cache::has($this->generateCacheName($cacheName, $contentType)); + } + + /** + * Remove a specific cache entry. + */ + public function forget(string $cacheName, ?string $contentType = null): bool + { + return Cache::forget($this->generateCacheName($cacheName, $contentType)); + } + + /** + * Retrieve from cache or execute the callback and store the result. + * + * @param string $cacheName + * @param \Closure $callback Produces the value when cache misses + * @param string|null $contentType + * @param int|null $duration TTL in seconds (null = config default) + * @return mixed + */ + public function remember( + string $cacheName, + \Closure $callback, + ?string $contentType = null, + ?int $duration = null + ): mixed { + $key = $this->generateCacheName($cacheName, $contentType); + + return Cache::remember($key, $duration ?? $this->defaultDuration(), $callback); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** + * Resolve the default TTL from config/cache_service.php. + * Falls back to 86400 (1 day) if the config key is missing. + */ + private function defaultDuration(): int + { + return (int) config('myapp.cacheDuration', 86400); + } + + /** + * Resolve and normalize the domain from the current HTTP request host. + * Falls back to 'cli' when running outside of an HTTP context (e.g. Artisan). + * + * Examples: + * "example.com" → "example_com" + * "api.example.com" → "api_example_com" + * "localhost:8000" → "localhost_8000" + */ + private function normalizeDomain(): string + { + $host = Request::getHost() ?: 'cli'; + + $host = strtolower($host); + $host = preg_replace('/[^a-z0-9]+/', '_', $host); + + return trim($host, '_'); + } +} \ No newline at end of file diff --git a/app/Services/DomainManagerService.php b/app/Services/DomainManagerService.php new file mode 100644 index 0000000..0e5a381 --- /dev/null +++ b/app/Services/DomainManagerService.php @@ -0,0 +1,223 @@ +initializeFromRequest($request); + } + + /** + * Initialize the service from the incoming request + */ + private function initializeFromRequest(Request $request): void + { + $this->currentHost = $request->getHost(); + + $this->slug = $this->determineSlug(); + $this->projectId = $this->resolveProjectId($this->slug); + + $this->updateApplicationConfig(); + } + + /** + * Determine the appropriate slug from environment or host + */ + private function determineSlug(): string + { + // Environment variable takes precedence + $envSlug = env('MY_PROJECT_SLUG'); + if (!empty($envSlug)) { + return $envSlug; + } + + // Otherwise, detect from host + return $this->detectSlugFromHost(); + } + + /** + * Extract slug from the current host + * Logic: ivnbg.com -> ivnbg, www.ivnbg.com -> ivnbg + */ + private function detectSlugFromHost(): string + { + if ($this->isLocalOrEmptyHost()) { + return self::DEFAULT_SLUG; + } + + $normalizedHost = $this->normalizeHost($this->currentHost); + + return $this->extractSlugFromHost($normalizedHost); + } + + /** + * Check if running in CLI mode or localhost + */ + private function isLocalOrEmptyHost(): bool + { + return empty($this->currentHost) || $this->currentHost === 'localhost'; + } + + /** + * Normalize host by removing www prefix + */ + private function normalizeHost(string $host): string + { + return Str::replace('www.', '', $host); + } + + /** + * Extract the slug from the host + * Root domain: removes extension and prefixes (e.g., domain-name.com -> domain-name) + * Subdomain: returns subdomain only (e.g., subdomain-name.domain.com -> subdomain-name) + */ + private function extractSlugFromHost(string $host): string + { + if (empty($host)) { + return self::DEFAULT_SLUG; + } + + return explode('.', $host)[0]; + } + + /** + * Resolve the project ID from the database using the slug + */ + private function resolveProjectId(string $slug): ?int + { + $cacheKey = $this->getCacheKey($slug); +$cache = new CacheService(); + return $cache->remember( + cacheName: $cacheKey, + callback: fn() => $this->fetchProjectIdFromDatabase($slug) , + contentType: $slug, + ); + } + + /** + * Fetch project ID from the database + */ + private function fetchProjectIdFromDatabase(string $slug): ?int + { + try { + return Project::where('slug', $slug)->value('id'); + } catch (QueryException $e) { + return null; + } + } + + /** + * Generate cache key for a given slug + */ + private function getCacheKey(string $slug): string + { + return self::CACHE_KEY_PREFIX . $slug; + } + + /** + * Update application configuration with the current slug + */ + private function updateApplicationConfig(): void + { + Config::set('myapp.projectSlug', $this->slug); + + + // Load project-specific config file + $this->loadProjectConfig($this->slug); + } + + private function loadProjectConfig(string $slug): void + { + $path = base_path("config/projects/{$slug}.php"); + + if (!file_exists($path)) { + return; + } + + $projectConfig = require $path; + + // Merge project config into myapp config + $current = Config::get('myapp', []); + Config::set('myapp', array_replace_recursive($current, $projectConfig)); + } + + /** + * Get the current slug + */ + public function getSlug(): string + { + return $this->slug; + } + + /** + * Get the current project ID + */ + public function getProjectId(): ?int + { + return $this->projectId; + } + + /** + * Manually set the slug (e.g., from an Artisan command) + * This automatically re-resolves the project ID and updates config + */ + public function setSlug(string $slug): self + { + $this->slug = $slug; + $this->projectId = $this->resolveProjectId($slug); + $this->updateApplicationConfig(); + + return $this; + } + + /** + * Manually override the project ID if needed + */ + public function setProjectId(int $projectId): self + { + $this->projectId = $projectId; + + return $this; + } + + /** + * Clear the cached project ID for a specific slug + */ + public function clearCache(?string $slug = null): void + { + $targetSlug = $slug ?? $this->slug; + $cacheKey = $this->getCacheKey($targetSlug); + + Cache::forget($cacheKey); + } + + /** + * Refresh the project ID from the database + */ + public function refresh(): self + { + $this->clearCache(); + $this->projectId = $this->resolveProjectId($this->slug); + + return $this; + } +} \ No newline at end of file diff --git a/app/Services/GitHub/DownloadPublishFiles.php b/app/Services/GitHub/DownloadPublishFiles.php new file mode 100644 index 0000000..f2a7139 --- /dev/null +++ b/app/Services/GitHub/DownloadPublishFiles.php @@ -0,0 +1,82 @@ + 'application/vnd.github+json', + 'User-Agent' => 'Laravel', + ])->get($url); + + if ($response->status() === 404) { + throw new \RuntimeException( + sprintf( + 'Repository "%s/%s" not found or branch "%s" does not exist.', + self::OWNER, self::REPO, self::BRANCH + ) + ); + } + + if ($response->failed()) { + throw new \RuntimeException( + 'GitHub API error [' . $response->status() . ']: ' . $response->body() + ); + } + + return collect($response->json('tree', [])) + ->where('type', 'blob') + ->pluck('path') + ->values() + ->all(); + } + + public static function downloadFiles(array $paths, callable $onFile = null): array + { + $results = []; + + foreach ($paths as $path) { + try { + $url = sprintf( + 'https://raw.githubusercontent.com/%s/%s/%s/%s', + self::OWNER, self::REPO, self::BRANCH, $path + ); + + $content = Http::get($url)->throw()->body(); + + $destination = storage_path('app/imports/' . $path); + + // Create directories if they don't exist + $directory = dirname($destination); + if (!is_dir($directory)) { + mkdir($directory, 0755, true); + } + + file_put_contents($destination, $content); + + $results[$path] = true; + } catch (\Throwable $e) { + $results[$path] = $e->getMessage(); + } + + if ($onFile) { + $onFile($path, $results[$path]); + } + } + + return $results; + } +} diff --git a/app/Services/Import/ProjectContentService.php b/app/Services/Import/ProjectContentService.php new file mode 100644 index 0000000..a248a9e --- /dev/null +++ b/app/Services/Import/ProjectContentService.php @@ -0,0 +1,376 @@ + content -> type). + * - Resolves Project IDs dynamically by using the folder name as a slug via the DomainManagerService. + * - Parses YAML Front Matter reliably using Spatie\YamlFrontMatter. + * - Uses DTOs for data integrity with automatic snake_case mapping to match your database columns. + * - Handles Mass Assignment safely by ensuring the Content model's $fillable array is correctly configured. + * - Captures Metadata that doesn't fit into standard columns into a JSON blob. + * + * Directory structure supported: + * content/{contentType}/ → no category assigned + * content/{contentType}/{category-slug}/ → category assigned via directory name + */ +class ProjectContentService +{ + private string $basePath; + private array $errors = []; + private array $success = []; + + public function getErrors(): array + { + return $this->errors; + } + + public function getSuccess(): array + { + return $this->success; + } + + public function __construct(protected DomainManagerService $domainManager) + { + $this->basePath = storage_path('app/imports/projects'); + } + + /** + * Main entry point to handle the import process. + */ + public function handle(): void + { + try { + if (!File::isDirectory($this->basePath)) { + throw new Exception("Import directory not found: {$this->basePath}"); + } + + $projectDirs = File::directories($this->basePath); + + foreach ($projectDirs as $projectPath) { + $projectName = basename($projectPath); + $this->processProject($projectName, $projectPath); + } + + $this->logSummary(); + } catch (Exception $e) { + Log::error("Import failed: " . $e->getMessage()); + } + } + + /** + * Process an individual project directory. + */ + private function processProject(string $projectName, string $projectPath): void + { + $contentPath = $projectPath . DIRECTORY_SEPARATOR . 'content'; + + if (!File::isDirectory($contentPath)) { + Log::warning("Project [{$projectName}] has no 'content' subdirectory. Skipping."); + return; + } + + // Get the project ID from config based on folder name + $this->domainManager->setSlug($projectName); + $projectId = $this->domainManager->getProjectId(); + + if (!$projectId) { + $this->errors[] = "Project configuration missing for: {$projectName}"; + return; + } + + $contentTypes = File::directories($contentPath); + + foreach ($contentTypes as $typePath) { + $contentTypeStr = basename($typePath); + $this->processContentType($projectId, $contentTypeStr, $typePath, $projectName); + } + } + + /** + * Process a contentType directory, supporting an optional category subdirectory level. + * + * Flat structure (no category): + * content/article/file.md → contentType=article, categorySlug=null + * + * Categorised structure: + * content/article/my-category/file.md → contentType=article, categorySlug='my-category' + * + * Both levels can coexist inside the same contentType directory. + */ + private function processContentType(int $projectId, string $contentTypeStr, string $path, string $projectSlug): void + { + // --- Files directly inside the contentType directory (no category) --- + foreach (File::files($path) as $file) { + if ($file->getExtension() !== 'md') { + continue; + } + + try { + $this->importFile($projectId, $contentTypeStr, $file->getPathname(), $projectSlug, null); + } catch (Exception $e) { + $this->errors[] = "File error [{$file->getFilename()}]: " . $e->getMessage(); + } + } + + // --- Files inside category subdirectories --- + foreach (File::directories($path) as $categoryPath) { + $categorySlug = basename($categoryPath); + + foreach (File::files($categoryPath) as $file) { + if ($file->getExtension() !== 'md') { + continue; + } + + try { + $this->importFile($projectId, $contentTypeStr, $file->getPathname(), $projectSlug, $categorySlug); + } catch (Exception $e) { + $this->errors[] = "File error [{$file->getFilename()}] (category: {$categorySlug}): " . $e->getMessage(); + } + } + } + } + + /** + * Parse the Markdown file and store it using the appropriate DTO. + * + * @param int $projectId + * @param string $contentTypeStr + * @param string $filePath + * @param string $projectSlug + * @param string|null $directoryCategorySlug Category slug derived from the parent directory name. + * When set, it takes precedence over the YAML 'categories' field. + */ + private function importFile( + int $projectId, + string $contentTypeStr, + string $filePath, + string $projectSlug, + ?string $directoryCategorySlug + ): void { + $fileContent = file_get_contents($filePath); + if ($fileContent === false) { + throw new Exception("Could not read file."); + } + + $object = YamlFrontMatter::parse($fileContent); + + // Basic validation: ensure we have at least a title or slug + if (!$object->matter('title') && !$object->matter('slug')) { + throw new Exception("Missing required YAML front matter (title or slug)."); + } + + $content = $this->handleContentImport($projectId, $contentTypeStr, $object, $projectSlug); + if (!$content) { + return; + } + + // Resolve the effective category: directory name wins over YAML front matter. + $effectiveCategorySlug = $directoryCategorySlug ?? $object->matter('categories'); + + $tagIds = $this->createTag($object->matter('tags') ?? '', $content); + if (count($tagIds) > 0) { + $content->tags()->sync($tagIds); + } + + $categoryIds = $effectiveCategorySlug + ? $this->getCategories($effectiveCategorySlug, $content) + : []; + + if (count($categoryIds) > 0) { + $content->categories()->sync($categoryIds); + } + } + + /** + * Handle generic content (articles, posts, etc.) + */ + private function handleContentImport(int $projectId, string $contentTypeStr, Document $object, string $projectSlug): ?Content + { + $content = null; + $slug = $object->matter('slug') ?? Str::slug($object->matter('title')); + try { + // Map YAML to ContentData DTO + $data = [ + 'uuid' => $object->matter('uuid') ?? (string)Str::uuid(), + 'projectId' => $projectId, + 'userId' => $object->matter('user_id') ?? 1, + 'authorId' => $object->matter('author_id'), + 'parentId' => $this->getParentId($object->matter('parent_id'), $contentTypeStr), + 'contentType' => ContentContentType::tryFrom($contentTypeStr) ?? ContentContentType::Article->value, + 'status' => ContentStatus::tryFrom($object->matter('status') ?? '') + ?? ContentStatus::Draft, + + 'visibility' => ContentVisibility::tryFrom($object->matter('visibility') ?? '') + ?? ContentVisibility::Public, + + 'lang' => Language::tryFrom($object->matter('lang') ?? '') + ?? Language::EN, + 'slug' => $slug, + 'title' => (string)$object->matter('title'), + 'subtitle' => $object->matter('subtitle'), + 'excerpt' => $object->matter('description') ?? $object->matter('excerpt'), + 'content' => $object->body(), + 'metadata' => $this->extractMetadata($object, ['title', 'slug', 'description', 'is_published']), + 'position' => (int)($object->matter('position') ?? 0), + 'isFeatured' => (bool)($object->matter('is_featured') ?? false), + 'publishedAt' => $object->matter('published_at') ? Carbon::parse($object->matter('published_at')) : null, + ]; + $data['metadata'] = $this->extractMetadata($object, array_keys($data), ['categories', 'tags', 'parent', 'is_featured']); + $data['metadata']['coverImage'] = $this->getContentImageUrl($projectSlug, $contentTypeStr, ($object->matter('imageDirectory') ?? '') . '/' . config('myapp.image.cover')); + $data['metadata']['featuredImage'] = $this->getContentImageUrl($projectSlug, $contentTypeStr, ($object->matter('imageDirectory') ?? '') . '/' . config('myapp.image.featured')); + if ($contentTypeStr === ContentContentType::Place->value) { + $data['metadata']['coverImage'] = $this->getPlaceImageUrl($projectSlug, $slug, config('myapp.image.cover')); + $data['metadata']['featuredImage'] = $this->getPlaceImageUrl($projectSlug, $slug, config('myapp.image.featured')); + } + + $dto = ContentData::from($data); + $content = Content::updateOrCreate( + ['slug' => $dto->slug, 'project_id' => $dto->projectId], + $dto->toArray() + ); + $this->success[] = "Imported Content: {$dto->title}"; + } catch (Exception $e) { + $this->errors[] = "Content Save Error [{$object->matter('title')}]: " . $e->getMessage(); + } + return $content; + } + + private function getContentImageUrl(string $projectSlug, string $contentType, string $filename): ?string + { + $imagePath = storage_path('app/public/' . $projectSlug . '/images/' . $contentType . '/' . $filename); + if (File::exists($imagePath)) { + return 'storage/' . $projectSlug . '/images/' . $contentType . '/' . $filename; + } + return null; + } + + private function getPlaceImageUrl(string $albumName, string $eventName, string $filename): ?string + { + $imagePath = storage_path('app/public/albums/' . $albumName . '/' . $eventName . '/' . $filename); + if (File::exists($imagePath)) { + return 'storage/albums/' . $albumName . '/' . $eventName . '/' . $filename; + } + return null; + } + + private function getParentId(string|null $parentSlug, string $contentType): ?int + { + if (empty($parentSlug)) { + return null; + } + + return Content::where('slug', $parentSlug) + ->publishedByType($contentType) + ->value('id'); + } + + private function getCategories(string $categories, Content $content): array + { + $categorySlugs = array_map('trim', explode(',', $categories)); + $categoryIds = []; + foreach ($categorySlugs as $slug) { + try { + $category = Category::where('slug', $slug)->publishedByType($content->content_type)->first(); + if (!$category) { + continue; + } + $this->success[] = 'Found category slug: ' . $slug . ' | content slug "' . $content->slug; + $categoryIds[] = $category->id; + } catch (Exception $e) { + $this->errors[] = "Category Save Error: " . $e->getMessage(); + continue; + } + } + return $categoryIds; + } + + private function createTag(string $tags, Content $content): array + { + $tagNames = array_map('trim', explode(',', $tags)); + $tagIds = []; + foreach ($tagNames as $tagName) { + if ($tagName === '') { + continue; + } + try { + $dto = TagData::from([ + 'projectId' => $content->project_id, + 'contentType' => $content->content_type, + 'lang' => $content->lang->value, + 'name' => $tagName, + ]); + + $tag = Tag::updateOrCreate( + ['name' => $dto->name, 'project_id' => $dto->projectId], + $dto->toArray() + ); + + if ($tag) { + $tagIds[] = $tag->id; + } + + $this->success[] = "Imported Tag: {$dto->name}"; + } catch (Exception $e) { + $this->errors[] = "Tag Save Error: " . $e->getMessage(); + continue; + } + } + return $tagIds; + } + + /** + * Extracts metadata from YAML front matter. + * + * @param Document $object The parsed markdown document + * @param array $excludeKeys Keys already mapped to DTO/Database columns (e.g., 'title', 'slug') + * @param array $ignoreKeys Keys that should be completely discarded (e.g., 'internal_note', 'temp_id') + * @return array + */ + private function extractMetadata(Document $object, array $excludeKeys, array $ignoreKeys = []): array + { + return array_filter($object->matter(), function ($key) use ($excludeKeys, $ignoreKeys) { + return !in_array($key, $excludeKeys) && !in_array($key, $ignoreKeys); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Log results of the import. + */ + private function logSummary(): void + { + foreach ($this->success as $msg) { + Log::info($msg); + } + + if (!empty($this->errors)) { + Log::error("Import completed with errors:"); + foreach ($this->errors as $error) { + Log::error($error); + } + } + } +} diff --git a/app/Traits/FilterByProject.php b/app/Traits/FilterByProject.php new file mode 100644 index 0000000..4d7d269 --- /dev/null +++ b/app/Traits/FilterByProject.php @@ -0,0 +1,37 @@ +getProjectId(); + + if ($projectId) { + $builder->where('project_id', $projectId); + } else { + // Safety: If no project identified, return no results + $builder->whereRaw('1 = 0'); + } + }); + + // 2. Auto-assign ID on Create + static::creating(function (Model $model) { + $manager = app(DomainManagerService::class); + if (!$model->project_id && $manager->getProjectId()) { + $model->project_id = $manager->getProjectId(); + } + }); + } +} diff --git a/app/Traits/HasDynamicContent.php b/app/Traits/HasDynamicContent.php new file mode 100644 index 0000000..2087599 --- /dev/null +++ b/app/Traits/HasDynamicContent.php @@ -0,0 +1,38 @@ +{$column}; + + if (empty($raw)) { + return ''; + } + + // 1. Render the Blade tags first + $renderedBlade = Blade::render($raw, $data); + + /** + * 2. Fix: Remove leading indentation. + * We use regex to remove spaces/tabs from the start of every line. + * This prevents Markdown from turning indented HTML into
 blocks.
+         */
+        $cleanedHtml = preg_replace('/^[ \t]+/m', '', $renderedBlade);
+
+        // 3. Render as Markdown
+        return Str::markdown($cleanedHtml);
+    }
+}
\ No newline at end of file
diff --git a/app/Traits/HasUuid.php b/app/Traits/HasUuid.php
new file mode 100644
index 0000000..d1d3a04
--- /dev/null
+++ b/app/Traits/HasUuid.php
@@ -0,0 +1,16 @@
+uuid = (string) Str::uuid();
+        });
+    }
+}
diff --git a/app/Traits/ImportProjectTrait.php b/app/Traits/ImportProjectTrait.php
new file mode 100644
index 0000000..56ed7af
--- /dev/null
+++ b/app/Traits/ImportProjectTrait.php
@@ -0,0 +1,151 @@
+errors;
+    }
+
+    private array $success = [];
+    public function getSuccess(): array
+    {
+        return $this->success;
+    }
+
+
+    private string $pathToDir;
+
+    private array $directories = [];
+    private function parseImportDir(): void
+    {
+
+        if (!config()->has('myapp.projects.' . $this->project)) {
+            throw new Exception('No valid project name: ' . $this->project);
+        }
+        if (!is_dir($this->pathToDir)) {
+            throw new Exception('Failed to read files from directory: ' . $this->pathToDir);
+        }
+
+        $directories = array_filter(glob($this->pathToDir . '/*'), 'is_dir');
+        if (empty($directories)) {
+            throw new Exception('No directories found in: ' . $this->pathToDir);
+        }
+
+        $this->directories = array_map('basename', $directories);
+    }
+
+    private function parseImportFiles(): void
+    {
+        foreach ($this->directories as $directory) {
+            $path = $this->pathToDir . $directory;
+            $files = glob($path . '/*.md');
+            if (empty($files)) {
+                $this->errors[] = 'WARNING: No files found in directory: ' . $path;
+                continue;
+            }
+            $this->files[$directory] = $files;
+        }
+    }
+
+    private function parseMarkdownFiles(): void
+    {
+        foreach ($this->files as $contentType => $files) {
+            foreach ($files as $file) {
+                $content = file_get_contents($file);
+                if ($content === false) {
+                    array_push($this->errors, 'Failed to read file: ' . $file);
+                    continue;
+                }
+                $object = YamlFrontMatter::parse($content);
+                $this->parseMarkdown($object, $contentType);
+
+            }
+        }
+    }
+    // Function takes all $object properties that are not listed in $data properties and creates an array that
+    // will be the content of options.
+
+    private function parseOptions(Document $object, $data): void
+    {
+        $data->options = array_filter((array) $object->matter(), function ($key) use ($data) {
+            return !property_exists($data, $key) && strpos($key, "\x00*\x00") === false;
+        },
+                                      ARRAY_FILTER_USE_KEY
+        );
+    }
+
+    private function getThumbImageUrl(string $album, string $event): string
+    {
+        $url = '';
+        $path = config('myapp.album.dir.target') . '/' . $album . '/' . $event. '/' . config('myapp.album.cover');
+        if (file_exists($path) ) {
+            $url = config('myapp.album.url'). '/' . $album . '/' . $event . '/' . config('myapp.album.cover');
+        }
+        return $url;
+        //return Utils::urlExists($url);
+    }
+    private function getFeaturedImageUrl(string $album, string $event): string
+    {
+        $url = '';
+        $path = config('myapp.album.dir.target') . '/' . $album . '/' . $event. '/' . config('myapp.album.featured');
+        if (file_exists($path) ) {
+            $url = config('myapp.album.url'). '/' . $album . '/' . $event . '/' . config('myapp.album.featured');
+        }
+        return $url;
+        //return Utils::urlExists($url);
+    }
+
+    private function getPlaceImageUrl(string $album, string $event): string
+    {
+        $url = '';
+        $path = config('myapp.album.dir.target') . '/' . $album . '/' . $event. '/' . config('myapp.album.cover');
+        if (file_exists($path) ) {
+            $url = config('myapp.album.url'). '/' . $album . '/' . $event . '/' . config('myapp.album.cover');
+        }
+       return $url;
+        //return Utils::urlExists($url);
+    }
+    private function getPlaceFeaturedImageUrl(string $album, string $event): string
+    {
+        $url = '';
+        $path = config('myapp.album.dir.target') . '/' . $album . '/' . $event. '/' . config('myapp.album.featured');
+        if (file_exists($path) ) {
+            $url = config('myapp.album.url'). '/' . $album . '/' . $event . '/' . config('myapp.album.featured');
+        }
+        return $url;
+        //return Utils::urlExists($url);
+    }
+
+    private function logErrors(): void
+    {
+        if(!empty($this->getErrors())) {
+            Log::error('Some errors occurred while importing data for project: ' . $this->project);
+            foreach ($this->getErrors() as $error) {
+                Log::error($error);
+            }
+        }
+    }
+
+    private function logSuccess(): void
+    {
+        foreach ($this->getSuccess() as $log) {
+            Log::info($log);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/Utils/ImageUtil.php b/app/Utils/ImageUtil.php
new file mode 100644
index 0000000..9e39a5c
--- /dev/null
+++ b/app/Utils/ImageUtil.php
@@ -0,0 +1,15 @@
+getData()['categoryType'] ?? null;
+        $view->with([
+                        'composerCurrentCategory'  => request()->query('category'),
+                        'composerCategory'       => [
+                            'article' => (new CategoryQuery(ContentContentType::Article->value))->all(),
+                            'place' => (new CategoryQuery(ContentContentType::Place->value))->all(),
+                        ],
+                    ]);
+    }
+}
\ No newline at end of file
diff --git a/app/View/Composers/TagComposer.php b/app/View/Composers/TagComposer.php
new file mode 100644
index 0000000..7202808
--- /dev/null
+++ b/app/View/Composers/TagComposer.php
@@ -0,0 +1,21 @@
+getData()['tagType'] ?? null;
+
+        $view->with([
+                        'composerTags' =>  (new TagQuery($contentType))->all(),
+                        'composerCurrentTag' =>  request()->query('tag', null),
+                    ]);
+    }
+}
\ No newline at end of file
diff --git a/boost.json b/boost.json
index c60d7b0..650547f 100644
--- a/boost.json
+++ b/boost.json
@@ -1,9 +1,9 @@
 {
     "agents": [
-        "copilot"
+        "gemini"
     ],
     "editors": [
-        "phpstorm"
+        "gemini"
     ],
     "guidelines": []
 }
diff --git a/bootstrap/providers.php b/bootstrap/providers.php
index 3ad94e3..00e1942 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -2,7 +2,7 @@
 
 return [
     App\Providers\AppServiceProvider::class,
-    App\Providers\FortifyServiceProvider::class,
+    App\Providers\MultiDomainServiceProvider::class,
     App\Providers\TelescopeServiceProvider::class,
     App\Providers\VoltServiceProvider::class,
 ];
diff --git a/composer.json b/composer.json
index 3560456..2ca9686 100644
--- a/composer.json
+++ b/composer.json
@@ -10,22 +10,26 @@
     "license": "MIT",
     "require": {
         "php": "^8.4",
-        "laravel/fortify": "^1.30",
+        "blade-ui-kit/blade-heroicons": "^2.6",
         "laravel/framework": "^12.0",
-        "laravel/telescope": "^5.16",
+        "laravel/telescope": "^5.18",
         "laravel/tinker": "^2.10.1",
         "livewire/volt": "^1.7.0",
+        "sheaf/cli": "^1.3",
         "spatie/laravel-data": "^4.18",
+        "spatie/laravel-query-builder": "^7.1",
         "spatie/laravel-sitemap": "^7.3",
         "spatie/laravel-sluggable": "^3.7",
-        "spatie/yaml-front-matter": "^2.1"
+        "spatie/yaml-front-matter": "^2.1",
+        "wireui/heroicons": "*"
     },
     "require-dev": {
         "fakerphp/faker": "^1.23",
+        "fruitcake/laravel-debugbar": "^4.0",
+        "joshembling/artisan-browse": "^0.0.7",
         "laradumps/laradumps": "^5.0",
         "laradumps/laradumps-core": "^4.0",
-        "laravel/boost": "^1.8",
-        "laravel/pail": "^1.2.2",
+        "laravel/boost": "*",
         "laravel/pint": "^1.24",
         "laravel/sail": "^1.41",
         "mockery/mockery": "^1.6",
@@ -67,7 +71,8 @@
             "@php artisan package:discover --ansi"
         ],
         "post-update-cmd": [
-            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+            "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
+            "@php artisan boost:update --ansi"
         ],
         "post-root-package-install": [
             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
@@ -79,6 +84,21 @@
         ],
         "pre-package-uninstall": [
             "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
+        ],
+        "clear": [
+            "@php artisan cache:clear",
+            "@php artisan config:clear",
+            "@php artisan route:clear",
+            "@php artisan view:clear",
+            "@php artisan event:clear",
+            "@php artisan schedule:clear-cache"
+        ],
+        "init-dev": [
+            "@clear",
+            "@php artisan migrate:fresh",
+            "@php artisan app:generate-album",
+            "@php artisan db:seed",
+            "@php artisan app:import-project-content"
         ]
     },
     "extra": {
diff --git a/composer.lock b/composer.lock
index 0989583..901a987 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,75 +4,170 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "b21e8ed2057bdecd484020111ab2e3bc",
+    "content-hash": "1be55184cae583a3b6ecb6088d39c326",
     "packages": [
         {
-            "name": "bacon/bacon-qr-code",
-            "version": "v3.0.3",
+            "name": "blade-ui-kit/blade-heroicons",
+            "version": "2.7.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Bacon/BaconQrCode.git",
-                "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563"
+                "url": "https://github.com/driesvints/blade-heroicons.git",
+                "reference": "66fa8ba09dba12e0cdb410b8cb94f3b890eca440"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/36a1cb2b81493fa5b82e50bf8068bf84d1542563",
-                "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563",
+                "url": "https://api.github.com/repos/driesvints/blade-heroicons/zipball/66fa8ba09dba12e0cdb410b8cb94f3b890eca440",
+                "reference": "66fa8ba09dba12e0cdb410b8cb94f3b890eca440",
                 "shasum": ""
             },
             "require": {
-                "dasprid/enum": "^1.0.3",
-                "ext-iconv": "*",
-                "php": "^8.1"
+                "blade-ui-kit/blade-icons": "^1.6",
+                "illuminate/support": "^9.0|^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.0"
             },
             "require-dev": {
-                "phly/keep-a-changelog": "^2.12",
-                "phpunit/phpunit": "^10.5.11 || ^11.0.4",
-                "spatie/phpunit-snapshot-assertions": "^5.1.5",
-                "spatie/pixelmatch-php": "^1.2.0",
-                "squizlabs/php_codesniffer": "^3.9"
+                "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0",
+                "phpunit/phpunit": "^9.0|^10.5|^11.0|^12.0"
             },
-            "suggest": {
-                "ext-imagick": "to generate QR code images"
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "BladeUI\\Heroicons\\BladeHeroiconsServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "BladeUI\\Heroicons\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Dries Vints",
+                    "homepage": "https://driesvints.com"
+                }
+            ],
+            "description": "A package to easily make use of Heroicons in your Laravel Blade views.",
+            "homepage": "https://github.com/driesvints/blade-heroicons",
+            "keywords": [
+                "Heroicons",
+                "blade",
+                "laravel"
+            ],
+            "support": {
+                "issues": "https://github.com/driesvints/blade-heroicons/issues",
+                "source": "https://github.com/driesvints/blade-heroicons/tree/2.7.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/driesvints",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.paypal.com/paypalme/driesvints",
+                    "type": "paypal"
+                }
+            ],
+            "time": "2026-03-16T13:00:23+00:00"
+        },
+        {
+            "name": "blade-ui-kit/blade-icons",
+            "version": "1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/driesvints/blade-icons.git",
+                "reference": "74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/driesvints/blade-icons/zipball/74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a",
+                "reference": "74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/view": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "php": "^7.4|^8.0",
+                "symfony/console": "^5.3|^6.0|^7.0|^8.0",
+                "symfony/finder": "^5.3|^6.0|^7.0|^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.5.1",
+                "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
+                "phpunit/phpunit": "^9.0|^10.5|^11.0"
             },
+            "bin": [
+                "bin/blade-icons-generate"
+            ],
             "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "BladeUI\\Icons\\BladeIconsServiceProvider"
+                    ]
+                }
+            },
             "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
                 "psr-4": {
-                    "BaconQrCode\\": "src/"
+                    "BladeUI\\Icons\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-2-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Ben Scholzen 'DASPRiD'",
-                    "email": "mail@dasprids.de",
-                    "homepage": "https://dasprids.de/",
-                    "role": "Developer"
+                    "name": "Dries Vints",
+                    "homepage": "https://driesvints.com"
                 }
             ],
-            "description": "BaconQrCode is a QR code generator for PHP.",
-            "homepage": "https://github.com/Bacon/BaconQrCode",
+            "description": "A package to easily make use of icons in your Laravel Blade views.",
+            "homepage": "https://github.com/driesvints/blade-icons",
+            "keywords": [
+                "blade",
+                "icons",
+                "laravel",
+                "svg"
+            ],
             "support": {
-                "issues": "https://github.com/Bacon/BaconQrCode/issues",
-                "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.3"
+                "issues": "https://github.com/driesvints/blade-icons/issues",
+                "source": "https://github.com/driesvints/blade-icons"
             },
-            "time": "2025-11-19T17:15:36+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/driesvints",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.paypal.com/paypalme/driesvints",
+                    "type": "paypal"
+                }
+            ],
+            "time": "2026-04-23T19:03:45+00:00"
         },
         {
             "name": "brick/math",
-            "version": "0.14.1",
+            "version": "0.14.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/brick/math.git",
-                "reference": "f05858549e5f9d7bb45875a75583240a38a281d0"
+                "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0",
-                "reference": "f05858549e5f9d7bb45875a75583240a38a281d0",
+                "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629",
+                "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629",
                 "shasum": ""
             },
             "require": {
@@ -111,7 +206,7 @@
             ],
             "support": {
                 "issues": "https://github.com/brick/math/issues",
-                "source": "https://github.com/brick/math/tree/0.14.1"
+                "source": "https://github.com/brick/math/tree/0.14.8"
             },
             "funding": [
                 {
@@ -119,7 +214,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-11-24T14:40:29+00:00"
+            "time": "2026-02-10T14:33:43+00:00"
         },
         {
             "name": "carbonphp/carbon-doctrine-types",
@@ -190,56 +285,6 @@
             ],
             "time": "2024-02-09T16:56:22+00:00"
         },
-        {
-            "name": "dasprid/enum",
-            "version": "1.0.7",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/DASPRiD/Enum.git",
-                "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
-                "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1 <9.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
-                "squizlabs/php_codesniffer": "*"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "DASPRiD\\Enum\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-2-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Ben Scholzen 'DASPRiD'",
-                    "email": "mail@dasprids.de",
-                    "homepage": "https://dasprids.de/",
-                    "role": "Developer"
-                }
-            ],
-            "description": "PHP 7.1 enum implementation",
-            "keywords": [
-                "enum",
-                "map"
-            ],
-            "support": {
-                "issues": "https://github.com/DASPRiD/Enum/issues",
-                "source": "https://github.com/DASPRiD/Enum/tree/1.0.7"
-            },
-            "time": "2025-09-16T12:23:56+00:00"
-        },
         {
             "name": "dflydev/dot-access-data",
             "version": "v3.0.3",
@@ -317,29 +362,29 @@
         },
         {
             "name": "doctrine/deprecations",
-            "version": "1.1.5",
+            "version": "1.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/deprecations.git",
-                "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38"
+                "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
-                "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
+                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
+                "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.1 || ^8.0"
             },
             "conflict": {
-                "phpunit/phpunit": "<=7.5 || >=13"
+                "phpunit/phpunit": "<=7.5 || >=14"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^9 || ^12 || ^13",
-                "phpstan/phpstan": "1.4.10 || 2.1.11",
+                "doctrine/coding-standard": "^9 || ^12 || ^14",
+                "phpstan/phpstan": "1.4.10 || 2.1.30",
                 "phpstan/phpstan-phpunit": "^1.0 || ^2",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0",
                 "psr/log": "^1 || ^2 || ^3"
             },
             "suggest": {
@@ -359,9 +404,9 @@
             "homepage": "https://www.doctrine-project.org/",
             "support": {
                 "issues": "https://github.com/doctrine/deprecations/issues",
-                "source": "https://github.com/doctrine/deprecations/tree/1.1.5"
+                "source": "https://github.com/doctrine/deprecations/tree/1.1.6"
             },
-            "time": "2025-04-07T20:06:18+00:00"
+            "time": "2026-02-07T07:09:04+00:00"
         },
         {
             "name": "doctrine/inflector",
@@ -1005,16 +1050,16 @@
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "2.8.0",
+            "version": "2.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "21dc724a0583619cd1652f673303492272778051"
+                "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
-                "reference": "21dc724a0583619cd1652f673303492272778051",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884",
+                "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884",
                 "shasum": ""
             },
             "require": {
@@ -1030,6 +1075,7 @@
             "require-dev": {
                 "bamarni/composer-bin-plugin": "^1.8.2",
                 "http-interop/http-factory-tests": "0.9.0",
+                "jshttp/mime-db": "1.54.0.1",
                 "phpunit/phpunit": "^8.5.44 || ^9.6.25"
             },
             "suggest": {
@@ -1101,7 +1147,7 @@
             ],
             "support": {
                 "issues": "https://github.com/guzzle/psr7/issues",
-                "source": "https://github.com/guzzle/psr7/tree/2.8.0"
+                "source": "https://github.com/guzzle/psr7/tree/2.9.0"
             },
             "funding": [
                 {
@@ -1117,7 +1163,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-08-23T21:21:41+00:00"
+            "time": "2026-03-10T16:41:02+00:00"
         },
         {
             "name": "guzzlehttp/uri-template",
@@ -1205,81 +1251,18 @@
             ],
             "time": "2025-08-22T14:27:06+00:00"
         },
-        {
-            "name": "laravel/fortify",
-            "version": "v1.33.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/laravel/fortify.git",
-                "reference": "e0666dabeec0b6428678af1d51f436dcfb24e3a9"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/laravel/fortify/zipball/e0666dabeec0b6428678af1d51f436dcfb24e3a9",
-                "reference": "e0666dabeec0b6428678af1d51f436dcfb24e3a9",
-                "shasum": ""
-            },
-            "require": {
-                "bacon/bacon-qr-code": "^3.0",
-                "ext-json": "*",
-                "illuminate/support": "^10.0|^11.0|^12.0",
-                "php": "^8.1",
-                "pragmarx/google2fa": "^9.0",
-                "symfony/console": "^6.0|^7.0"
-            },
-            "require-dev": {
-                "orchestra/testbench": "^8.36|^9.15|^10.8",
-                "phpstan/phpstan": "^1.10"
-            },
-            "type": "library",
-            "extra": {
-                "laravel": {
-                    "providers": [
-                        "Laravel\\Fortify\\FortifyServiceProvider"
-                    ]
-                },
-                "branch-alias": {
-                    "dev-master": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Laravel\\Fortify\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Taylor Otwell",
-                    "email": "taylor@laravel.com"
-                }
-            ],
-            "description": "Backend controllers and scaffolding for Laravel authentication.",
-            "keywords": [
-                "auth",
-                "laravel"
-            ],
-            "support": {
-                "issues": "https://github.com/laravel/fortify/issues",
-                "source": "https://github.com/laravel/fortify"
-            },
-            "time": "2025-12-15T14:48:33+00:00"
-        },
         {
             "name": "laravel/framework",
-            "version": "v12.44.0",
+            "version": "v12.57.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "592bbf1c036042958332eb98e3e8131b29102f33"
+                "reference": "63a6ced3db46582b3276e2d03770a6317a94d6e2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/592bbf1c036042958332eb98e3e8131b29102f33",
-                "reference": "592bbf1c036042958332eb98e3e8131b29102f33",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/63a6ced3db46582b3276e2d03770a6317a94d6e2",
+                "reference": "63a6ced3db46582b3276e2d03770a6317a94d6e2",
                 "shasum": ""
             },
             "require": {
@@ -1300,7 +1283,7 @@
                 "guzzlehttp/uri-template": "^1.0",
                 "laravel/prompts": "^0.3.0",
                 "laravel/serializable-closure": "^1.3|^2.0",
-                "league/commonmark": "^2.7",
+                "league/commonmark": "^2.8.1",
                 "league/flysystem": "^3.25.1",
                 "league/flysystem-local": "^3.25.1",
                 "league/uri": "^7.5.1",
@@ -1320,8 +1303,8 @@
                 "symfony/mailer": "^7.2.0",
                 "symfony/mime": "^7.2.0",
                 "symfony/polyfill-php83": "^1.33",
-                "symfony/polyfill-php84": "^1.33",
-                "symfony/polyfill-php85": "^1.33",
+                "symfony/polyfill-php84": "^1.34",
+                "symfony/polyfill-php85": "^1.34",
                 "symfony/process": "^7.2.0",
                 "symfony/routing": "^7.2.0",
                 "symfony/uid": "^7.2.0",
@@ -1392,10 +1375,10 @@
                 "league/flysystem-sftp-v3": "^3.25.1",
                 "mockery/mockery": "^1.6.10",
                 "opis/json-schema": "^2.4.1",
-                "orchestra/testbench-core": "^10.8.1",
+                "orchestra/testbench-core": "^10.9.0",
                 "pda/pheanstalk": "^5.0.6|^7.0.0",
                 "php-http/discovery": "^1.15",
-                "phpstan/phpstan": "^2.0",
+                "phpstan/phpstan": "^2.1.41",
                 "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1",
                 "predis/predis": "^2.3|^3.0",
                 "resend/resend-php": "^0.10.0|^1.0",
@@ -1488,34 +1471,34 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2025-12-23T15:29:43+00:00"
+            "time": "2026-04-22T13:21:29+00:00"
         },
         {
             "name": "laravel/prompts",
-            "version": "v0.3.8",
+            "version": "v0.3.17",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/prompts.git",
-                "reference": "096748cdfb81988f60090bbb839ce3205ace0d35"
+                "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35",
-                "reference": "096748cdfb81988f60090bbb839ce3205ace0d35",
+                "url": "https://api.github.com/repos/laravel/prompts/zipball/6a82ac19a28b916ae0885828795dbd4c59d9a818",
+                "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818",
                 "shasum": ""
             },
             "require": {
                 "composer-runtime-api": "^2.2",
                 "ext-mbstring": "*",
                 "php": "^8.1",
-                "symfony/console": "^6.2|^7.0"
+                "symfony/console": "^6.2|^7.0|^8.0"
             },
             "conflict": {
                 "illuminate/console": ">=10.17.0 <10.25.0",
                 "laravel/framework": ">=10.17.0 <10.25.0"
             },
             "require-dev": {
-                "illuminate/collections": "^10.0|^11.0|^12.0",
+                "illuminate/collections": "^10.0|^11.0|^12.0|^13.0",
                 "mockery/mockery": "^1.5",
                 "pestphp/pest": "^2.3|^3.4|^4.0",
                 "phpstan/phpstan": "^1.12.28",
@@ -1545,33 +1528,89 @@
             "description": "Add beautiful and user-friendly forms to your command-line applications.",
             "support": {
                 "issues": "https://github.com/laravel/prompts/issues",
-                "source": "https://github.com/laravel/prompts/tree/v0.3.8"
+                "source": "https://github.com/laravel/prompts/tree/v0.3.17"
+            },
+            "time": "2026-04-20T16:07:33+00:00"
+        },
+        {
+            "name": "laravel/sentinel",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/sentinel.git",
+                "reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/sentinel/zipball/972d9885d9d14312a118e9565c4e6ecc5e751ea1",
+                "reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "illuminate/container": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.0"
+            },
+            "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"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Laravel\\Sentinel\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                },
+                {
+                    "name": "Mior Muhammad Zaki",
+                    "email": "mior@laravel.com"
+                }
+            ],
+            "support": {
+                "source": "https://github.com/laravel/sentinel/tree/v1.1.0"
             },
-            "time": "2025-11-21T20:52:52+00:00"
+            "time": "2026-03-24T14:03:38+00:00"
         },
         {
             "name": "laravel/serializable-closure",
-            "version": "v2.0.7",
+            "version": "v2.0.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/serializable-closure.git",
-                "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd"
+                "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
-                "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
+                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/a6abb4e54f6fcd3138120b9ad497f0bd146f9919",
+                "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919",
                 "shasum": ""
             },
             "require": {
                 "php": "^8.1"
             },
             "require-dev": {
-                "illuminate/support": "^10.0|^11.0|^12.0",
+                "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"
+                "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0"
             },
             "type": "library",
             "extra": {
@@ -1608,34 +1647,35 @@
                 "issues": "https://github.com/laravel/serializable-closure/issues",
                 "source": "https://github.com/laravel/serializable-closure"
             },
-            "time": "2025-11-21T20:52:36+00:00"
+            "time": "2026-04-14T13:33:34+00:00"
         },
         {
             "name": "laravel/telescope",
-            "version": "v5.16.0",
+            "version": "v5.20.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/telescope.git",
-                "reference": "a868e91a0912d6a44363636f7467a8578db83026"
+                "reference": "38ec6e6006a67e05e0c476c5f8ef3550b72e43d8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/telescope/zipball/a868e91a0912d6a44363636f7467a8578db83026",
-                "reference": "a868e91a0912d6a44363636f7467a8578db83026",
+                "url": "https://api.github.com/repos/laravel/telescope/zipball/38ec6e6006a67e05e0c476c5f8ef3550b72e43d8",
+                "reference": "38ec6e6006a67e05e0c476c5f8ef3550b72e43d8",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0",
+                "laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
+                "laravel/sentinel": "^1.0",
                 "php": "^8.0",
-                "symfony/console": "^5.3|^6.0|^7.0",
-                "symfony/var-dumper": "^5.0|^6.0|^7.0"
+                "symfony/console": "^5.3|^6.0|^7.0|^8.0",
+                "symfony/var-dumper": "^5.0|^6.0|^7.0|^8.0"
             },
             "require-dev": {
                 "ext-gd": "*",
                 "guzzlehttp/guzzle": "^6.0|^7.0",
                 "laravel/octane": "^1.4|^2.0",
-                "orchestra/testbench": "^6.47.1|^7.55|^8.36|^9.15|^10.8",
+                "orchestra/testbench": "^6.47.1|^7.55|^8.36|^9.15|^10.8|^11.0",
                 "phpstan/phpstan": "^1.10"
             },
             "type": "library",
@@ -1674,22 +1714,22 @@
             ],
             "support": {
                 "issues": "https://github.com/laravel/telescope/issues",
-                "source": "https://github.com/laravel/telescope/tree/v5.16.0"
+                "source": "https://github.com/laravel/telescope/tree/v5.20.0"
             },
-            "time": "2025-12-09T13:34:29+00:00"
+            "time": "2026-04-06T12:52:26+00:00"
         },
         {
             "name": "laravel/tinker",
-            "version": "v2.10.2",
+            "version": "v2.11.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/tinker.git",
-                "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c"
+                "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c",
-                "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c",
+                "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741",
+                "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741",
                 "shasum": ""
             },
             "require": {
@@ -1698,7 +1738,7 @@
                 "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"
+                "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0"
             },
             "require-dev": {
                 "mockery/mockery": "~1.3.3|^1.4.2",
@@ -1740,22 +1780,22 @@
             ],
             "support": {
                 "issues": "https://github.com/laravel/tinker/issues",
-                "source": "https://github.com/laravel/tinker/tree/v2.10.2"
+                "source": "https://github.com/laravel/tinker/tree/v2.11.1"
             },
-            "time": "2025-11-20T16:29:12+00:00"
+            "time": "2026-02-06T14:12:35+00:00"
         },
         {
             "name": "league/commonmark",
-            "version": "2.8.0",
+            "version": "2.8.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/commonmark.git",
-                "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb"
+                "reference": "59fb075d2101740c337c7216e3f32b36c204218b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
-                "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b",
+                "reference": "59fb075d2101740c337c7216e3f32b36c204218b",
                 "shasum": ""
             },
             "require": {
@@ -1780,9 +1820,9 @@
                 "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",
+                "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0",
+                "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0",
+                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0",
                 "unleashedtech/php-coding-standard": "^3.1.1",
                 "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
             },
@@ -1849,7 +1889,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-11-26T21:48:24+00:00"
+            "time": "2026-03-19T13:16:38+00:00"
         },
         {
             "name": "league/config",
@@ -1935,16 +1975,16 @@
         },
         {
             "name": "league/flysystem",
-            "version": "3.30.2",
+            "version": "3.33.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/flysystem.git",
-                "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277"
+                "reference": "570b8871e0ce693764434b29154c54b434905350"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277",
-                "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350",
+                "reference": "570b8871e0ce693764434b29154c54b434905350",
                 "shasum": ""
             },
             "require": {
@@ -2012,22 +2052,22 @@
             ],
             "support": {
                 "issues": "https://github.com/thephpleague/flysystem/issues",
-                "source": "https://github.com/thephpleague/flysystem/tree/3.30.2"
+                "source": "https://github.com/thephpleague/flysystem/tree/3.33.0"
             },
-            "time": "2025-11-10T17:13:11+00:00"
+            "time": "2026-03-25T07:59:30+00:00"
         },
         {
             "name": "league/flysystem-local",
-            "version": "3.30.2",
+            "version": "3.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/flysystem-local.git",
-                "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d"
+                "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d",
-                "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d",
+                "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079",
+                "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079",
                 "shasum": ""
             },
             "require": {
@@ -2061,9 +2101,9 @@
                 "local"
             ],
             "support": {
-                "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2"
+                "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0"
             },
-            "time": "2025-11-10T11:23:37+00:00"
+            "time": "2026-01-23T15:30:45+00:00"
         },
         {
             "name": "league/mime-type-detection",
@@ -2123,20 +2163,20 @@
         },
         {
             "name": "league/uri",
-            "version": "7.7.0",
+            "version": "7.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/uri.git",
-                "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807"
+                "reference": "08cf38e3924d4f56238125547b5720496fac8fd4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
-                "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
+                "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4",
+                "reference": "08cf38e3924d4f56238125547b5720496fac8fd4",
                 "shasum": ""
             },
             "require": {
-                "league/uri-interfaces": "^7.7",
+                "league/uri-interfaces": "^7.8.1",
                 "php": "^8.1",
                 "psr/http-factory": "^1"
             },
@@ -2150,11 +2190,11 @@
                 "ext-gmp": "to improve IPV4 host parsing",
                 "ext-intl": "to handle IDN host with the best performance",
                 "ext-uri": "to use the PHP native URI class",
-                "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
-                "league/uri-components": "Needed to easily manipulate URI objects components",
-                "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP",
+                "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
+                "league/uri-components": "to provide additional tools to manipulate URI objects components",
+                "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
                 "php-64bit": "to improve IPV4 host parsing",
-                "rowbot/url": "to handle WHATWG URL",
+                "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
                 "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
             },
             "type": "library",
@@ -2209,7 +2249,7 @@
                 "docs": "https://uri.thephpleague.com",
                 "forum": "https://thephpleague.slack.com",
                 "issues": "https://github.com/thephpleague/uri-src/issues",
-                "source": "https://github.com/thephpleague/uri/tree/7.7.0"
+                "source": "https://github.com/thephpleague/uri/tree/7.8.1"
             },
             "funding": [
                 {
@@ -2217,20 +2257,20 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-07T16:02:06+00:00"
+            "time": "2026-03-15T20:22:25+00:00"
         },
         {
             "name": "league/uri-interfaces",
-            "version": "7.7.0",
+            "version": "7.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/uri-interfaces.git",
-                "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c"
+                "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
-                "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
+                "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928",
+                "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928",
                 "shasum": ""
             },
             "require": {
@@ -2243,7 +2283,7 @@
                 "ext-gmp": "to improve IPV4 host parsing",
                 "ext-intl": "to handle IDN host with the best performance",
                 "php-64bit": "to improve IPV4 host parsing",
-                "rowbot/url": "to handle WHATWG URL",
+                "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
                 "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
             },
             "type": "library",
@@ -2293,7 +2333,7 @@
                 "docs": "https://uri.thephpleague.com",
                 "forum": "https://thephpleague.slack.com",
                 "issues": "https://github.com/thephpleague/uri-src/issues",
-                "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0"
+                "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1"
             },
             "funding": [
                 {
@@ -2301,40 +2341,40 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-07T16:03:21+00:00"
+            "time": "2026-03-08T20:05:35+00:00"
         },
         {
             "name": "livewire/livewire",
-            "version": "v3.7.3",
+            "version": "v4.2.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/livewire/livewire.git",
-                "reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c"
+                "reference": "7d0bfa46269b1ec186b8cdd38baffee5cc647d10"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/livewire/livewire/zipball/a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
-                "reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
+                "url": "https://api.github.com/repos/livewire/livewire/zipball/7d0bfa46269b1ec186b8cdd38baffee5cc647d10",
+                "reference": "7d0bfa46269b1ec186b8cdd38baffee5cc647d10",
                 "shasum": ""
             },
             "require": {
-                "illuminate/database": "^10.0|^11.0|^12.0",
-                "illuminate/routing": "^10.0|^11.0|^12.0",
-                "illuminate/support": "^10.0|^11.0|^12.0",
-                "illuminate/validation": "^10.0|^11.0|^12.0",
+                "illuminate/database": "^10.0|^11.0|^12.0|^13.0",
+                "illuminate/routing": "^10.0|^11.0|^12.0|^13.0",
+                "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+                "illuminate/validation": "^10.0|^11.0|^12.0|^13.0",
                 "laravel/prompts": "^0.1.24|^0.2|^0.3",
                 "league/mime-type-detection": "^1.9",
                 "php": "^8.1",
-                "symfony/console": "^6.0|^7.0",
-                "symfony/http-kernel": "^6.2|^7.0"
+                "symfony/console": "^6.0|^7.0|^8.0",
+                "symfony/http-kernel": "^6.2|^7.0|^8.0"
             },
             "require-dev": {
                 "calebporzio/sushi": "^2.1",
-                "laravel/framework": "^10.15.0|^11.0|^12.0",
+                "laravel/framework": "^10.15.0|^11.0|^12.0|^13.0",
                 "mockery/mockery": "^1.3.1",
-                "orchestra/testbench": "^8.21.0|^9.0|^10.0",
-                "orchestra/testbench-dusk": "^8.24|^9.1|^10.0",
-                "phpunit/phpunit": "^10.4|^11.5",
+                "orchestra/testbench": "^8.21.0|^9.0|^10.0|^11.0",
+                "orchestra/testbench-dusk": "^8.24|^9.1|^10.0|^11.0",
+                "phpunit/phpunit": "^10.4|^11.5|^12.5",
                 "psy/psysh": "^0.11.22|^0.12"
             },
             "type": "library",
@@ -2369,7 +2409,7 @@
             "description": "A front-end framework for Laravel.",
             "support": {
                 "issues": "https://github.com/livewire/livewire/issues",
-                "source": "https://github.com/livewire/livewire/tree/v3.7.3"
+                "source": "https://github.com/livewire/livewire/tree/v4.2.4"
             },
             "funding": [
                 {
@@ -2377,32 +2417,32 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-19T02:00:29+00:00"
+            "time": "2026-04-02T20:48:35+00:00"
         },
         {
             "name": "livewire/volt",
-            "version": "v1.10.1",
+            "version": "v1.10.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/livewire/volt.git",
-                "reference": "48cff133990c6261c63ee279fc091af6f6c6654e"
+                "reference": "32a111951779f9dcf2a08a5704acb940ac9a146c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/livewire/volt/zipball/48cff133990c6261c63ee279fc091af6f6c6654e",
-                "reference": "48cff133990c6261c63ee279fc091af6f6c6654e",
+                "url": "https://api.github.com/repos/livewire/volt/zipball/32a111951779f9dcf2a08a5704acb940ac9a146c",
+                "reference": "32a111951779f9dcf2a08a5704acb940ac9a146c",
                 "shasum": ""
             },
             "require": {
-                "laravel/framework": "^10.38.2|^11.0|^12.0",
+                "laravel/framework": "^10.38.2|^11.0|^12.0|^13.0",
                 "livewire/livewire": "^3.6.1|^4.0",
                 "php": "^8.1"
             },
             "require-dev": {
                 "laravel/folio": "^1.1",
-                "orchestra/testbench": "^8.36|^9.15|^10.8",
+                "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0",
                 "pestphp/pest": "^2.9.5|^3.0|^4.0",
-                "phpstan/phpstan": "^1.10"
+                "phpstan/phpstan": "^1.10|^2.1"
             },
             "type": "library",
             "extra": {
@@ -2448,7 +2488,7 @@
                 "issues": "https://github.com/livewire/volt/issues",
                 "source": "https://github.com/livewire/volt"
             },
-            "time": "2025-11-25T16:19:15+00:00"
+            "time": "2026-03-18T14:16:30+00:00"
         },
         {
             "name": "monolog/monolog",
@@ -2555,16 +2595,16 @@
         },
         {
             "name": "nesbot/carbon",
-            "version": "3.11.0",
+            "version": "3.11.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/CarbonPHP/carbon.git",
-                "reference": "bdb375400dcd162624531666db4799b36b64e4a1"
+                "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1",
-                "reference": "bdb375400dcd162624531666db4799b36b64e4a1",
+                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60",
+                "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60",
                 "shasum": ""
             },
             "require": {
@@ -2588,7 +2628,7 @@
                 "phpstan/extension-installer": "^1.4.3",
                 "phpstan/phpstan": "^2.1.22",
                 "phpunit/phpunit": "^10.5.53",
-                "squizlabs/php_codesniffer": "^3.13.4"
+                "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
             },
             "bin": [
                 "bin/carbon"
@@ -2631,14 +2671,14 @@
                 }
             ],
             "description": "An API extension for DateTime that supports 281 different languages.",
-            "homepage": "https://carbon.nesbot.com",
+            "homepage": "https://carbonphp.github.io/carbon/",
             "keywords": [
                 "date",
                 "datetime",
                 "time"
             ],
             "support": {
-                "docs": "https://carbon.nesbot.com/docs",
+                "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
                 "issues": "https://github.com/CarbonPHP/carbon/issues",
                 "source": "https://github.com/CarbonPHP/carbon"
             },
@@ -2656,20 +2696,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-02T21:04:28+00:00"
+            "time": "2026-04-07T09:57:54+00:00"
         },
         {
             "name": "nette/schema",
-            "version": "v1.3.3",
+            "version": "v1.3.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nette/schema.git",
-                "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004"
+                "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004",
-                "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004",
+                "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002",
+                "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002",
                 "shasum": ""
             },
             "require": {
@@ -2677,8 +2717,10 @@
                 "php": "8.1 - 8.5"
             },
             "require-dev": {
-                "nette/tester": "^2.5.2",
-                "phpstan/phpstan-nette": "^2.0@stable",
+                "nette/phpstan-rules": "^1.0",
+                "nette/tester": "^2.6",
+                "phpstan/extension-installer": "^1.4@stable",
+                "phpstan/phpstan": "^2.1.39@stable",
                 "tracy/tracy": "^2.8"
             },
             "type": "library",
@@ -2719,22 +2761,22 @@
             ],
             "support": {
                 "issues": "https://github.com/nette/schema/issues",
-                "source": "https://github.com/nette/schema/tree/v1.3.3"
+                "source": "https://github.com/nette/schema/tree/v1.3.5"
             },
-            "time": "2025-10-30T22:57:59+00:00"
+            "time": "2026-02-23T03:47:12+00:00"
         },
         {
             "name": "nette/utils",
-            "version": "v4.1.1",
+            "version": "v4.1.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nette/utils.git",
-                "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72"
+                "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72",
-                "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72",
+                "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe",
+                "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe",
                 "shasum": ""
             },
             "require": {
@@ -2746,8 +2788,10 @@
             },
             "require-dev": {
                 "jetbrains/phpstorm-attributes": "^1.2",
+                "nette/phpstan-rules": "^1.0",
                 "nette/tester": "^2.5",
-                "phpstan/phpstan-nette": "^2.0@stable",
+                "phpstan/extension-installer": "^1.4@stable",
+                "phpstan/phpstan": "^2.1@stable",
                 "tracy/tracy": "^2.9"
             },
             "suggest": {
@@ -2808,9 +2852,9 @@
             ],
             "support": {
                 "issues": "https://github.com/nette/utils/issues",
-                "source": "https://github.com/nette/utils/tree/v4.1.1"
+                "source": "https://github.com/nette/utils/tree/v4.1.3"
             },
-            "time": "2025-12-22T12:14:32+00:00"
+            "time": "2026-02-13T03:05:33+00:00"
         },
         {
             "name": "nicmart/tree",
@@ -2926,31 +2970,31 @@
         },
         {
             "name": "nunomaduro/termwind",
-            "version": "v2.3.3",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nunomaduro/termwind.git",
-                "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017"
+                "reference": "712a31b768f5daea284c2169a7d227031001b9a8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017",
-                "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017",
+                "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8",
+                "reference": "712a31b768f5daea284c2169a7d227031001b9a8",
                 "shasum": ""
             },
             "require": {
                 "ext-mbstring": "*",
                 "php": "^8.2",
-                "symfony/console": "^7.3.6"
+                "symfony/console": "^7.4.4 || ^8.0.4"
             },
             "require-dev": {
-                "illuminate/console": "^11.46.1",
-                "laravel/pint": "^1.25.1",
+                "illuminate/console": "^11.47.0",
+                "laravel/pint": "^1.27.1",
                 "mockery/mockery": "^1.6.12",
-                "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3",
+                "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2",
                 "phpstan/phpstan": "^1.12.32",
                 "phpstan/phpstan-strict-rules": "^1.6.2",
-                "symfony/var-dumper": "^7.3.5",
+                "symfony/var-dumper": "^7.3.5 || ^8.0.4",
                 "thecodingmachine/phpstan-strict-rules": "^1.0.0"
             },
             "type": "library",
@@ -2982,7 +3026,7 @@
                     "email": "enunomaduro@gmail.com"
                 }
             ],
-            "description": "Its like Tailwind CSS, but for the console.",
+            "description": "It's like Tailwind CSS, but for the console.",
             "keywords": [
                 "cli",
                 "console",
@@ -2993,7 +3037,7 @@
             ],
             "support": {
                 "issues": "https://github.com/nunomaduro/termwind/issues",
-                "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3"
+                "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0"
             },
             "funding": [
                 {
@@ -3009,89 +3053,20 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-11-20T02:34:59+00:00"
-        },
-        {
-            "name": "paragonie/constant_time_encoding",
-            "version": "v3.1.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/paragonie/constant_time_encoding.git",
-                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
-                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^8"
-            },
-            "require-dev": {
-                "infection/infection": "^0",
-                "nikic/php-fuzzer": "^0",
-                "phpunit/phpunit": "^9|^10|^11",
-                "vimeo/psalm": "^4|^5|^6"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "ParagonIE\\ConstantTime\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Paragon Initiative Enterprises",
-                    "email": "security@paragonie.com",
-                    "homepage": "https://paragonie.com",
-                    "role": "Maintainer"
-                },
-                {
-                    "name": "Steve 'Sc00bz' Thomas",
-                    "email": "steve@tobtu.com",
-                    "homepage": "https://www.tobtu.com",
-                    "role": "Original Developer"
-                }
-            ],
-            "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
-            "keywords": [
-                "base16",
-                "base32",
-                "base32_decode",
-                "base32_encode",
-                "base64",
-                "base64_decode",
-                "base64_encode",
-                "bin2hex",
-                "encoding",
-                "hex",
-                "hex2bin",
-                "rfc4648"
-            ],
-            "support": {
-                "email": "info@paragonie.com",
-                "issues": "https://github.com/paragonie/constant_time_encoding/issues",
-                "source": "https://github.com/paragonie/constant_time_encoding"
-            },
-            "time": "2025-09-24T15:06:41+00:00"
+            "time": "2026-02-16T23:10:27+00:00"
         },
         {
             "name": "phpdocumentor/reflection",
-            "version": "6.4.4",
+            "version": "6.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/Reflection.git",
-                "reference": "5e5db15b34e6eae755cb97beaa7fe076ae9e8d4c"
+                "reference": "c8d36446027506a005103d57265ba5ea56beabfc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/5e5db15b34e6eae755cb97beaa7fe076ae9e8d4c",
-                "reference": "5e5db15b34e6eae755cb97beaa7fe076ae9e8d4c",
+                "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/c8d36446027506a005103d57265ba5ea56beabfc",
+                "reference": "c8d36446027506a005103d57265ba5ea56beabfc",
                 "shasum": ""
             },
             "require": {
@@ -3148,9 +3123,9 @@
             ],
             "support": {
                 "issues": "https://github.com/phpDocumentor/Reflection/issues",
-                "source": "https://github.com/phpDocumentor/Reflection/tree/6.4.4"
+                "source": "https://github.com/phpDocumentor/Reflection/tree/6.6.0"
             },
-            "time": "2025-11-25T21:21:18+00:00"
+            "time": "2026-04-12T18:07:10+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
@@ -3207,16 +3182,16 @@
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "5.6.6",
+            "version": "5.6.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8"
+                "reference": "31a105931bc8ffa3a123383829772e832fd8d903"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8",
-                "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903",
+                "reference": "31a105931bc8ffa3a123383829772e832fd8d903",
                 "shasum": ""
             },
             "require": {
@@ -3265,9 +3240,9 @@
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
             "support": {
                 "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6"
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7"
             },
-            "time": "2025-12-22T21:13:58+00:00"
+            "time": "2026-03-18T20:47:46+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
@@ -3404,16 +3379,16 @@
         },
         {
             "name": "phpstan/phpdoc-parser",
-            "version": "2.3.0",
+            "version": "2.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpdoc-parser.git",
-                "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495"
+                "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495",
-                "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495",
+                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
+                "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
                 "shasum": ""
             },
             "require": {
@@ -3445,61 +3420,9 @@
             "description": "PHPDoc parser with support for nullable, intersection and generic types",
             "support": {
                 "issues": "https://github.com/phpstan/phpdoc-parser/issues",
-                "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0"
+                "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
             },
-            "time": "2025-08-30T15:50:23+00:00"
-        },
-        {
-            "name": "pragmarx/google2fa",
-            "version": "v9.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/antonioribeiro/google2fa.git",
-                "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf",
-                "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf",
-                "shasum": ""
-            },
-            "require": {
-                "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0",
-                "php": "^7.1|^8.0"
-            },
-            "require-dev": {
-                "phpstan/phpstan": "^1.9",
-                "phpunit/phpunit": "^7.5.15|^8.5|^9.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "PragmaRX\\Google2FA\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Antonio Carlos Ribeiro",
-                    "email": "acr@antoniocarlosribeiro.com",
-                    "role": "Creator & Designer"
-                }
-            ],
-            "description": "A One Time Password Authentication package, compatible with Google Authenticator.",
-            "keywords": [
-                "2fa",
-                "Authentication",
-                "Two Factor Authentication",
-                "google2fa"
-            ],
-            "support": {
-                "issues": "https://github.com/antonioribeiro/google2fa/issues",
-                "source": "https://github.com/antonioribeiro/google2fa/tree/v9.0.0"
-            },
-            "time": "2025-09-19T22:51:08+00:00"
+            "time": "2026-01-25T14:56:51+00:00"
         },
         {
             "name": "psr/clock",
@@ -3915,16 +3838,16 @@
         },
         {
             "name": "psy/psysh",
-            "version": "v0.12.18",
+            "version": "v0.12.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/bobthecow/psysh.git",
-                "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196"
+                "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ddff0ac01beddc251786fe70367cd8bbdb258196",
-                "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196",
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f",
+                "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f",
                 "shasum": ""
             },
             "require": {
@@ -3988,9 +3911,9 @@
             ],
             "support": {
                 "issues": "https://github.com/bobthecow/psysh/issues",
-                "source": "https://github.com/bobthecow/psysh/tree/v0.12.18"
+                "source": "https://github.com/bobthecow/psysh/tree/v0.12.22"
             },
-            "time": "2025-12-17T14:35:46+00:00"
+            "time": "2026-03-22T23:03:24+00:00"
         },
         {
             "name": "ralouphie/getallheaders",
@@ -4191,17 +4114,77 @@
             "time": "2025-12-14T04:43:48+00:00"
         },
         {
-            "name": "spatie/browsershot",
-            "version": "5.2.0",
+            "name": "sheaf/cli",
+            "version": "v1.3.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/spatie/browsershot.git",
-                "reference": "9bc6b8d67175810d7a399b2588c3401efe2d02a8"
+                "url": "https://github.com/sheafui/cli.git",
+                "reference": "4bf1d1959f7a4c18b0d1388e6bb3509ce6253f79"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/browsershot/zipball/9bc6b8d67175810d7a399b2588c3401efe2d02a8",
-                "reference": "9bc6b8d67175810d7a399b2588c3401efe2d02a8",
+                "url": "https://api.github.com/repos/sheafui/cli/zipball/4bf1d1959f7a4c18b0d1388e6bb3509ce6253f79",
+                "reference": "4bf1d1959f7a4c18b0d1388e6bb3509ce6253f79",
+                "shasum": ""
+            },
+            "require-dev": {
+                "orchestra/testbench": "^10.4",
+                "pestphp/pest": "^3.8",
+                "pestphp/pest-plugin-laravel": "^3.2"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Sheaf\\Cli\\ServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Sheaf\\Cli\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Youssef ACHCHIRAJ",
+                    "email": "achchiraj.youssef@gmail.com",
+                    "homepage": "https://youssef-achchiraj.com",
+                    "role": "Full Stack Developer"
+                }
+            ],
+            "description": "A CLI tool for Sheaf UI",
+            "homepage": "https://github.com/sheaf/cli",
+            "keywords": [
+                "artisan-command",
+                "cli",
+                "laravel",
+                "laravel-package",
+                "package",
+                "sheaf"
+            ],
+            "support": {
+                "issues": "https://github.com/sheafui/cli/issues",
+                "source": "https://github.com/sheafui/cli/tree/v1.3.4"
+            },
+            "time": "2025-11-30T14:34:29+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": {
@@ -4248,7 +4231,7 @@
                 "webpage"
             ],
             "support": {
-                "source": "https://github.com/spatie/browsershot/tree/5.2.0"
+                "source": "https://github.com/spatie/browsershot/tree/5.2.3"
             },
             "funding": [
                 {
@@ -4256,26 +4239,26 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-22T10:02:16+00:00"
+            "time": "2026-02-18T16:10:58+00:00"
         },
         {
             "name": "spatie/crawler",
-            "version": "8.4.7",
+            "version": "8.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/crawler.git",
-                "reference": "67cbd569437d0e35b1332c5f21d009cac8b4a37b"
+                "reference": "18198a2198adff1637c0028cb60d2c9559721556"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/crawler/zipball/67cbd569437d0e35b1332c5f21d009cac8b4a37b",
-                "reference": "67cbd569437d0e35b1332c5f21d009cac8b4a37b",
+                "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",
+                "illuminate/collections": "^10.0|^11.0|^12.0|^13.0",
                 "nicmart/tree": "^0.10",
                 "php": "^8.2",
                 "spatie/browsershot": "^5.0.5",
@@ -4283,7 +4266,7 @@
                 "symfony/dom-crawler": "^6.0|^7.0|^8.0"
             },
             "require-dev": {
-                "pestphp/pest": "^2.0|^3.0",
+                "pestphp/pest": "^2.0|^3.0|^4.0",
                 "spatie/ray": "^1.37"
             },
             "type": "library",
@@ -4312,7 +4295,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/crawler/issues",
-                "source": "https://github.com/spatie/crawler/tree/8.4.7"
+                "source": "https://github.com/spatie/crawler/tree/8.5.0"
             },
             "funding": [
                 {
@@ -4324,24 +4307,24 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-11-26T17:35:15+00:00"
+            "time": "2026-02-21T22:52:37+00:00"
         },
         {
             "name": "spatie/laravel-data",
-            "version": "4.18.0",
+            "version": "4.22.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/laravel-data.git",
-                "reference": "c10784f1133d540a702bd6db36ed659f4bc0606a"
+                "reference": "cec66f8d53bedbf78bea9f3155c7ee49fa8a61fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/laravel-data/zipball/c10784f1133d540a702bd6db36ed659f4bc0606a",
-                "reference": "c10784f1133d540a702bd6db36ed659f4bc0606a",
+                "url": "https://api.github.com/repos/spatie/laravel-data/zipball/cec66f8d53bedbf78bea9f3155c7ee49fa8a61fb",
+                "reference": "cec66f8d53bedbf78bea9f3155c7ee49fa8a61fb",
                 "shasum": ""
             },
             "require": {
-                "illuminate/contracts": "^10.0|^11.0|^12.0",
+                "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
                 "php": "^8.1",
                 "phpdocumentor/reflection": "^6.0",
                 "spatie/laravel-package-tools": "^1.9.0",
@@ -4351,16 +4334,15 @@
                 "fakerphp/faker": "^1.14",
                 "friendsofphp/php-cs-fixer": "^3.0",
                 "inertiajs/inertia-laravel": "^2.0",
-                "livewire/livewire": "^3.0",
+                "livewire/livewire": "^3.0|^4.0",
                 "mockery/mockery": "^1.6",
                 "nesbot/carbon": "^2.63|^3.0",
-                "orchestra/testbench": "^8.0|^9.0|^10.0",
-                "pestphp/pest": "^2.31|^3.0",
-                "pestphp/pest-plugin-laravel": "^2.0|^3.0",
-                "pestphp/pest-plugin-livewire": "^2.1|^3.0",
+                "orchestra/testbench": "^8.37.0|^9.16|^10.9|^11.0",
+                "pestphp/pest": "^2.36|^3.8|^4.3",
+                "pestphp/pest-plugin-laravel": "^2.4|^3.0|^4.0",
+                "pestphp/pest-plugin-livewire": "^2.1|^3.0|^4.0",
                 "phpbench/phpbench": "^1.2",
                 "phpstan/extension-installer": "^1.1",
-                "phpunit/phpunit": "^10.0|^11.0|^12.0",
                 "spatie/invade": "^1.0",
                 "spatie/laravel-typescript-transformer": "^2.5",
                 "spatie/pest-plugin-snapshots": "^2.1",
@@ -4399,7 +4381,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/laravel-data/issues",
-                "source": "https://github.com/spatie/laravel-data/tree/4.18.0"
+                "source": "https://github.com/spatie/laravel-data/tree/4.22.0"
             },
             "funding": [
                 {
@@ -4407,33 +4389,33 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-10-16T16:44:07+00:00"
+            "time": "2026-04-16T13:26:30+00:00"
         },
         {
             "name": "spatie/laravel-package-tools",
-            "version": "1.92.7",
+            "version": "1.93.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/laravel-package-tools.git",
-                "reference": "f09a799850b1ed765103a4f0b4355006360c49a5"
+                "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5",
-                "reference": "f09a799850b1ed765103a4f0b4355006360c49a5",
+                "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
+                "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
                 "shasum": ""
             },
             "require": {
-                "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0",
-                "php": "^8.0"
+                "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.1"
             },
             "require-dev": {
                 "mockery/mockery": "^1.5",
-                "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0",
-                "pestphp/pest": "^1.23|^2.1|^3.1",
-                "phpunit/php-code-coverage": "^9.0|^10.0|^11.0",
-                "phpunit/phpunit": "^9.5.24|^10.5|^11.5",
-                "spatie/pest-plugin-test-time": "^1.1|^2.2"
+                "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": {
@@ -4460,7 +4442,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/laravel-package-tools/issues",
-                "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7"
+                "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0"
             },
             "funding": [
                 {
@@ -4468,35 +4450,109 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-07-17T15:46:43+00:00"
+            "time": "2026-02-21T12:49:54+00:00"
+        },
+        {
+            "name": "spatie/laravel-query-builder",
+            "version": "7.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/laravel-query-builder.git",
+                "reference": "72b8c2675b8b0642658b5ad83bfef3b6ee77b28f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/72b8c2675b8b0642658b5ad83bfef3b6ee77b28f",
+                "reference": "72b8c2675b8b0642658b5ad83bfef3b6ee77b28f",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/database": "^12.0|^13.0",
+                "illuminate/http": "^12.0|^13.0",
+                "illuminate/support": "^12.0|^13.0",
+                "php": "^8.3",
+                "spatie/laravel-package-tools": "^1.11"
+            },
+            "require-dev": {
+                "ext-json": "*",
+                "larastan/larastan": "^3.3",
+                "mockery/mockery": "^1.4",
+                "orchestra/testbench": "^10.0|^11.0",
+                "pestphp/pest": "^4.0",
+                "phpunit/phpunit": "^12.0",
+                "spatie/invade": "^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Spatie\\QueryBuilder\\QueryBuilderServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\QueryBuilder\\": "src",
+                    "Spatie\\QueryBuilder\\Database\\Factories\\": "database/factories"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Alex Vanderbist",
+                    "email": "alex@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Easily build Eloquent queries from API requests",
+            "homepage": "https://github.com/spatie/laravel-query-builder",
+            "keywords": [
+                "laravel-query-builder",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/laravel-query-builder/issues",
+                "source": "https://github.com/spatie/laravel-query-builder"
+            },
+            "funding": [
+                {
+                    "url": "https://spatie.be/open-source/support-us",
+                    "type": "custom"
+                }
+            ],
+            "time": "2026-04-17T07:37:24+00:00"
         },
         {
             "name": "spatie/laravel-sitemap",
-            "version": "7.3.8",
+            "version": "7.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/laravel-sitemap.git",
-                "reference": "9ff614d4834ada564aed5ed88507c9e5baab8e51"
+                "reference": "52397c6f4219a8a0a163ddb8a8d5bc5f9bba0df4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/laravel-sitemap/zipball/9ff614d4834ada564aed5ed88507c9e5baab8e51",
-                "reference": "9ff614d4834ada564aed5ed88507c9e5baab8e51",
+                "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",
+                "illuminate/support": "^11.0|^12.0||^13.0",
                 "nesbot/carbon": "^2.71|^3.0",
-                "php": "^8.2||^8.3||^8.4",
+                "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",
-                "pestphp/pest": "^3.7.4",
+                "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"
@@ -4533,7 +4589,7 @@
                 "spatie"
             ],
             "support": {
-                "source": "https://github.com/spatie/laravel-sitemap/tree/7.3.8"
+                "source": "https://github.com/spatie/laravel-sitemap/tree/7.4.0"
             },
             "funding": [
                 {
@@ -4541,30 +4597,30 @@
                     "type": "custom"
                 }
             ],
-            "time": "2025-11-25T21:06:08+00:00"
+            "time": "2026-02-21T23:00:46+00:00"
         },
         {
             "name": "spatie/laravel-sluggable",
-            "version": "3.7.5",
+            "version": "3.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/laravel-sluggable.git",
-                "reference": "e4fdd519e043a2af02b52eec2c3be2dd2e262e27"
+                "reference": "3624a2d7d8c8475d8561c53faf63f1af583ef76c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/laravel-sluggable/zipball/e4fdd519e043a2af02b52eec2c3be2dd2e262e27",
-                "reference": "e4fdd519e043a2af02b52eec2c3be2dd2e262e27",
+                "url": "https://api.github.com/repos/spatie/laravel-sluggable/zipball/3624a2d7d8c8475d8561c53faf63f1af583ef76c",
+                "reference": "3624a2d7d8c8475d8561c53faf63f1af583ef76c",
                 "shasum": ""
             },
             "require": {
-                "illuminate/database": "^8.0|^9.0|^10.0|^11.0|^12.0",
-                "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0",
-                "php": "^8.0"
+                "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": "^6.23|^7.0|^8.0|^9.0|^10.0",
-                "pestphp/pest": "^1.20|^2.0|^3.7",
+                "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",
@@ -4592,7 +4648,7 @@
                 "spatie"
             ],
             "support": {
-                "source": "https://github.com/spatie/laravel-sluggable/tree/3.7.5"
+                "source": "https://github.com/spatie/laravel-sluggable/tree/3.8.1"
             },
             "funding": [
                 {
@@ -4600,33 +4656,33 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-04-24T09:21:00+00:00"
+            "time": "2026-03-23T07:42:02+00:00"
         },
         {
             "name": "spatie/php-structure-discoverer",
-            "version": "2.3.3",
+            "version": "2.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/php-structure-discoverer.git",
-                "reference": "552a5b974a9853a32e5677a66e85ae615a96a90b"
+                "reference": "53565b3c4770c061b08e8ab755d109344e847799"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/552a5b974a9853a32e5677a66e85ae615a96a90b",
-                "reference": "552a5b974a9853a32e5677a66e85ae615a96a90b",
+                "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/53565b3c4770c061b08e8ab755d109344e847799",
+                "reference": "53565b3c4770c061b08e8ab755d109344e847799",
                 "shasum": ""
             },
             "require": {
-                "illuminate/collections": "^11.0|^12.0",
+                "illuminate/collections": "^11.0|^12.0|^13.0",
                 "php": "^8.3",
                 "spatie/laravel-package-tools": "^1.92.7",
                 "symfony/finder": "^6.0|^7.3.5|^8.0"
             },
             "require-dev": {
                 "amphp/parallel": "^2.3.2",
-                "illuminate/console": "^11.0|^12.0",
+                "illuminate/console": "^11.0|^12.0|^13.0",
                 "nunomaduro/collision": "^7.0|^8.8.3",
-                "orchestra/testbench": "^9.5|^10.8",
+                "orchestra/testbench": "^9.5|^10.8|^11.0",
                 "pestphp/pest": "^3.8|^4.0",
                 "pestphp/pest-plugin-laravel": "^3.2|^4.0",
                 "phpstan/extension-installer": "^1.4.3",
@@ -4671,7 +4727,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/php-structure-discoverer/issues",
-                "source": "https://github.com/spatie/php-structure-discoverer/tree/2.3.3"
+                "source": "https://github.com/spatie/php-structure-discoverer/tree/2.4.1"
             },
             "funding": [
                 {
@@ -4679,20 +4735,20 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-11-24T16:41:01+00:00"
+            "time": "2026-04-17T07:00:45+00:00"
         },
         {
             "name": "spatie/robots-txt",
-            "version": "2.5.3",
+            "version": "2.5.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/robots-txt.git",
-                "reference": "edb91c798ec70583d41c131019da45fa167af5e8"
+                "reference": "a8dd35d0a94e863f52509a366a634978e9c1db03"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/robots-txt/zipball/edb91c798ec70583d41c131019da45fa167af5e8",
-                "reference": "edb91c798ec70583d41c131019da45fa167af5e8",
+                "url": "https://api.github.com/repos/spatie/robots-txt/zipball/a8dd35d0a94e863f52509a366a634978e9c1db03",
+                "reference": "a8dd35d0a94e863f52509a366a634978e9c1db03",
                 "shasum": ""
             },
             "require": {
@@ -4727,7 +4783,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/robots-txt/issues",
-                "source": "https://github.com/spatie/robots-txt/tree/2.5.3"
+                "source": "https://github.com/spatie/robots-txt/tree/2.5.4"
             },
             "funding": [
                 {
@@ -4739,20 +4795,20 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-11-20T13:00:33+00:00"
+            "time": "2026-02-25T07:59:20+00:00"
         },
         {
             "name": "spatie/temporary-directory",
-            "version": "2.3.0",
+            "version": "2.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/temporary-directory.git",
-                "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b"
+                "reference": "662e481d6ec07ef29fd05010433428851a42cd07"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b",
-                "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b",
+                "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07",
+                "reference": "662e481d6ec07ef29fd05010433428851a42cd07",
                 "shasum": ""
             },
             "require": {
@@ -4788,7 +4844,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/temporary-directory/issues",
-                "source": "https://github.com/spatie/temporary-directory/tree/2.3.0"
+                "source": "https://github.com/spatie/temporary-directory/tree/2.3.1"
             },
             "funding": [
                 {
@@ -4800,7 +4856,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-01-13T13:04:43+00:00"
+            "time": "2026-01-12T07:42:22+00:00"
         },
         {
             "name": "spatie/yaml-front-matter",
@@ -4866,16 +4922,16 @@
         },
         {
             "name": "symfony/clock",
-            "version": "v8.0.0",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/clock.git",
-                "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f"
+                "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
-                "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
+                "url": "https://api.github.com/repos/symfony/clock/zipball/b55a638b189a6faa875e0ccdb00908fb87af95b3",
+                "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3",
                 "shasum": ""
             },
             "require": {
@@ -4919,7 +4975,7 @@
                 "time"
             ],
             "support": {
-                "source": "https://github.com/symfony/clock/tree/v8.0.0"
+                "source": "https://github.com/symfony/clock/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -4939,20 +4995,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-11-12T15:46:48+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6"
+                "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6",
-                "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6",
+                "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
+                "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
                 "shasum": ""
             },
             "require": {
@@ -5017,7 +5073,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v7.4.3"
+                "source": "https://github.com/symfony/console/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5037,20 +5093,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-23T14:50:43+00:00"
+            "time": "2026-03-30T13:54:39+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v8.0.0",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b"
+                "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/6225bd458c53ecdee056214cb4a2ffaf58bd592b",
-                "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed",
+                "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed",
                 "shasum": ""
             },
             "require": {
@@ -5086,7 +5142,7 @@
             "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v8.0.0"
+                "source": "https://github.com/symfony/css-selector/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -5106,7 +5162,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-10-30T14:17:19+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
@@ -5177,16 +5233,16 @@
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v8.0.1",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "11a3dbe6f6c0ae03ae71f879cf5c2e28db107179"
+                "reference": "284ace90732b445b027728b5e0eec6418a17a364"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11a3dbe6f6c0ae03ae71f879cf5c2e28db107179",
-                "reference": "11a3dbe6f6c0ae03ae71f879cf5c2e28db107179",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/284ace90732b445b027728b5e0eec6418a17a364",
+                "reference": "284ace90732b445b027728b5e0eec6418a17a364",
                 "shasum": ""
             },
             "require": {
@@ -5223,7 +5279,7 @@
             "description": "Eases DOM navigation for HTML and XML documents",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/dom-crawler/tree/v8.0.1"
+                "source": "https://github.com/symfony/dom-crawler/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -5243,20 +5299,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-06T17:00:47+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/error-handler",
-            "version": "v7.4.0",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/error-handler.git",
-                "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2"
+                "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2",
-                "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2",
+                "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa",
+                "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa",
                 "shasum": ""
             },
             "require": {
@@ -5305,7 +5361,7 @@
             "description": "Provides tools to manage errors and ease debugging PHP code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/error-handler/tree/v7.4.0"
+                "source": "https://github.com/symfony/error-handler/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5325,20 +5381,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-11-05T14:29:59+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v8.0.0",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "573f95783a2ec6e38752979db139f09fec033f03"
+                "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03",
-                "reference": "573f95783a2ec6e38752979db139f09fec033f03",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6",
+                "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6",
                 "shasum": ""
             },
             "require": {
@@ -5390,7 +5446,7 @@
             "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0"
+                "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -5410,7 +5466,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-10-30T14:17:19+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/event-dispatcher-contracts",
@@ -5490,16 +5546,16 @@
         },
         {
             "name": "symfony/finder",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "fffe05569336549b20a1be64250b40516d6e8d06"
+                "reference": "e0be088d22278583a82da281886e8c3592fbf149"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06",
-                "reference": "fffe05569336549b20a1be64250b40516d6e8d06",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149",
+                "reference": "e0be088d22278583a82da281886e8c3592fbf149",
                 "shasum": ""
             },
             "require": {
@@ -5534,7 +5590,7 @@
             "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v7.4.3"
+                "source": "https://github.com/symfony/finder/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5554,20 +5610,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-23T14:50:43+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52"
+                "reference": "9381209597ec66c25be154cbf2289076e64d1eab"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52",
-                "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9381209597ec66c25be154cbf2289076e64d1eab",
+                "reference": "9381209597ec66c25be154cbf2289076e64d1eab",
                 "shasum": ""
             },
             "require": {
@@ -5616,7 +5672,7 @@
             "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v7.4.3"
+                "source": "https://github.com/symfony/http-foundation/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5636,20 +5692,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-23T14:23:49+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "885211d4bed3f857b8c964011923528a55702aa5"
+                "reference": "017e76ad089bac281553389269e259e155935e1a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5",
-                "reference": "885211d4bed3f857b8c964011923528a55702aa5",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/017e76ad089bac281553389269e259e155935e1a",
+                "reference": "017e76ad089bac281553389269e259e155935e1a",
                 "shasum": ""
             },
             "require": {
@@ -5691,7 +5747,7 @@
                 "symfony/config": "^6.4|^7.0|^8.0",
                 "symfony/console": "^6.4|^7.0|^8.0",
                 "symfony/css-selector": "^6.4|^7.0|^8.0",
-                "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+                "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0",
                 "symfony/dom-crawler": "^6.4|^7.0|^8.0",
                 "symfony/expression-language": "^6.4|^7.0|^8.0",
                 "symfony/finder": "^6.4|^7.0|^8.0",
@@ -5735,7 +5791,7 @@
             "description": "Provides a structured process for converting a Request into a Response",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-kernel/tree/v7.4.3"
+                "source": "https://github.com/symfony/http-kernel/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5755,20 +5811,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-31T08:43:57+00:00"
+            "time": "2026-03-31T20:57:01+00:00"
         },
         {
             "name": "symfony/mailer",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/mailer.git",
-                "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4"
+                "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4",
-                "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4",
+                "url": "https://api.github.com/repos/symfony/mailer/zipball/f6ea532250b476bfc1b56699b388a1bdbf168f62",
+                "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62",
                 "shasum": ""
             },
             "require": {
@@ -5819,7 +5875,7 @@
             "description": "Helps sending emails",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/mailer/tree/v7.4.3"
+                "source": "https://github.com/symfony/mailer/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5839,20 +5895,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-16T08:02:06+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/mime",
-            "version": "v7.4.0",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/mime.git",
-                "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a"
+                "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a",
-                "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/6df02f99998081032da3407a8d6c4e1dcb5d4379",
+                "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379",
                 "shasum": ""
             },
             "require": {
@@ -5863,15 +5919,15 @@
             },
             "conflict": {
                 "egulias/email-validator": "~3.0.0",
-                "phpdocumentor/reflection-docblock": "<3.2.2",
-                "phpdocumentor/type-resolver": "<1.4.0",
+                "phpdocumentor/reflection-docblock": "<5.2|>=7",
+                "phpdocumentor/type-resolver": "<1.5.1",
                 "symfony/mailer": "<6.4",
                 "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
             },
             "require-dev": {
                 "egulias/email-validator": "^2.1.10|^3.1|^4",
                 "league/html-to-markdown": "^5.0",
-                "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
+                "phpdocumentor/reflection-docblock": "^5.2|^6.0",
                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
                 "symfony/process": "^6.4|^7.0|^8.0",
                 "symfony/property-access": "^6.4|^7.0|^8.0",
@@ -5908,7 +5964,7 @@
                 "mime-type"
             ],
             "support": {
-                "source": "https://github.com/symfony/mime/tree/v7.4.0"
+                "source": "https://github.com/symfony/mime/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -5928,20 +5984,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-11-16T10:14:42+00:00"
+            "time": "2026-03-30T14:11:46+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
-                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
                 "shasum": ""
             },
             "require": {
@@ -5991,7 +6047,7 @@
                 "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6011,20 +6067,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-09-09T11:45:10+00:00"
+            "time": "2026-04-10T16:19:22+00:00"
         },
         {
             "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
+                "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
-                "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
+                "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
                 "shasum": ""
             },
             "require": {
@@ -6073,7 +6129,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6093,11 +6149,11 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-06-27T09:58:17+00:00"
+            "time": "2026-04-10T16:19:22+00:00"
         },
         {
             "name": "symfony/polyfill-intl-idn",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-idn.git",
@@ -6160,7 +6216,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6184,7 +6240,7 @@
         },
         {
             "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -6245,7 +6301,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6269,16 +6325,16 @@
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+                "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
-                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
+                "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
                 "shasum": ""
             },
             "require": {
@@ -6330,7 +6386,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6350,20 +6406,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-12-23T08:48:59+00:00"
+            "time": "2026-04-10T17:25:58+00:00"
         },
         {
             "name": "symfony/polyfill-php80",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
-                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
                 "shasum": ""
             },
             "require": {
@@ -6414,7 +6470,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6434,20 +6490,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-01-02T08:10:11+00:00"
+            "time": "2026-04-10T16:19:22+00:00"
         },
         {
             "name": "symfony/polyfill-php83",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php83.git",
-                "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
+                "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
-                "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149",
+                "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149",
                 "shasum": ""
             },
             "require": {
@@ -6494,7 +6550,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-php83/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6514,20 +6570,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-07-08T02:45:35+00:00"
+            "time": "2026-04-10T17:25:58+00:00"
         },
         {
             "name": "symfony/polyfill-php84",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php84.git",
-                "reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
+                "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
-                "reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
+                "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06",
+                "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06",
                 "shasum": ""
             },
             "require": {
@@ -6574,7 +6630,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6594,20 +6650,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-06-24T13:30:11+00:00"
+            "time": "2026-04-10T18:47:49+00:00"
         },
         {
             "name": "symfony/polyfill-php85",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php85.git",
-                "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
+                "reference": "2c408a6bb0313e6001a83628dc5506100474254e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
-                "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
+                "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/2c408a6bb0313e6001a83628dc5506100474254e",
+                "reference": "2c408a6bb0313e6001a83628dc5506100474254e",
                 "shasum": ""
             },
             "require": {
@@ -6654,7 +6710,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-php85/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6674,20 +6730,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-06-23T16:12:55+00:00"
+            "time": "2026-04-10T16:50:15+00:00"
         },
         {
             "name": "symfony/polyfill-uuid",
-            "version": "v1.33.0",
+            "version": "v1.36.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-uuid.git",
-                "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
+                "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
-                "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
+                "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94",
+                "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94",
                 "shasum": ""
             },
             "require": {
@@ -6737,7 +6793,7 @@
                 "uuid"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0"
+                "source": "https://github.com/symfony/polyfill-uuid/tree/v1.36.0"
             },
             "funding": [
                 {
@@ -6757,20 +6813,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-09-09T11:45:10+00:00"
+            "time": "2026-04-10T16:19:22+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f"
+                "reference": "60f19cd3badc8de688421e21e4305eba50f8089a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f",
-                "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f",
+                "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a",
+                "reference": "60f19cd3badc8de688421e21e4305eba50f8089a",
                 "shasum": ""
             },
             "require": {
@@ -6802,7 +6858,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v7.4.3"
+                "source": "https://github.com/symfony/process/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -6822,20 +6878,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-19T10:00:43+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/routing",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/routing.git",
-                "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090"
+                "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090",
-                "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b",
+                "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b",
                 "shasum": ""
             },
             "require": {
@@ -6887,7 +6943,7 @@
                 "url"
             ],
             "support": {
-                "source": "https://github.com/symfony/routing/tree/v7.4.3"
+                "source": "https://github.com/symfony/routing/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -6907,7 +6963,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-19T10:00:43+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/service-contracts",
@@ -6998,16 +7054,16 @@
         },
         {
             "name": "symfony/string",
-            "version": "v8.0.1",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
+                "reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
-                "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
+                "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
+                "reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
                 "shasum": ""
             },
             "require": {
@@ -7064,7 +7120,7 @@
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v8.0.1"
+                "source": "https://github.com/symfony/string/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -7084,20 +7140,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-01T09:13:36+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v8.0.3",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "60a8f11f0e15c48f2cc47c4da53873bb5b62135d"
+                "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/60a8f11f0e15c48f2cc47c4da53873bb5b62135d",
-                "reference": "60a8f11f0e15c48f2cc47c4da53873bb5b62135d",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f",
+                "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f",
                 "shasum": ""
             },
             "require": {
@@ -7157,7 +7213,7 @@
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v8.0.3"
+                "source": "https://github.com/symfony/translation/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -7177,7 +7233,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-21T10:59:45+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "symfony/translation-contracts",
@@ -7263,16 +7319,16 @@
         },
         {
             "name": "symfony/uid",
-            "version": "v7.4.0",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/uid.git",
-                "reference": "2498e9f81b7baa206f44de583f2f48350b90142c"
+                "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c",
-                "reference": "2498e9f81b7baa206f44de583f2f48350b90142c",
+                "url": "https://api.github.com/repos/symfony/uid/zipball/6883ebdf7bf6a12b37519dbc0df62b0222401b56",
+                "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56",
                 "shasum": ""
             },
             "require": {
@@ -7317,7 +7373,7 @@
                 "uuid"
             ],
             "support": {
-                "source": "https://github.com/symfony/uid/tree/v7.4.0"
+                "source": "https://github.com/symfony/uid/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -7337,20 +7393,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-09-25T11:02:55+00:00"
+            "time": "2026-03-24T13:12:05+00:00"
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v7.4.3",
+            "version": "v7.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "7e99bebcb3f90d8721890f2963463280848cba92"
+                "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92",
-                "reference": "7e99bebcb3f90d8721890f2963463280848cba92",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
+                "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
                 "shasum": ""
             },
             "require": {
@@ -7404,7 +7460,7 @@
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v7.4.3"
+                "source": "https://github.com/symfony/var-dumper/tree/v7.4.8"
             },
             "funding": [
                 {
@@ -7424,32 +7480,31 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-18T07:04:31+00:00"
+            "time": "2026-03-30T13:44:50+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v7.4.1",
+            "version": "v8.0.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "24dd4de28d2e3988b311751ac49e684d783e2345"
+                "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345",
-                "reference": "24dd4de28d2e3988b311751ac49e684d783e2345",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1",
+                "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.2",
-                "symfony/deprecation-contracts": "^2.5|^3",
+                "php": ">=8.4",
                 "symfony/polyfill-ctype": "^1.8"
             },
             "conflict": {
-                "symfony/console": "<6.4"
+                "symfony/console": "<7.4"
             },
             "require-dev": {
-                "symfony/console": "^6.4|^7.0|^8.0"
+                "symfony/console": "^7.4|^8.0"
             },
             "bin": [
                 "Resources/bin/yaml-lint"
@@ -7480,7 +7535,7 @@
             "description": "Loads and dumps YAML files",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/yaml/tree/v7.4.1"
+                "source": "https://github.com/symfony/yaml/tree/v8.0.8"
             },
             "funding": [
                 {
@@ -7500,7 +7555,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-04T18:11:45+00:00"
+            "time": "2026-03-30T15:14:47+00:00"
         },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
@@ -7643,23 +7698,23 @@
         },
         {
             "name": "voku/portable-ascii",
-            "version": "2.0.3",
+            "version": "2.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/voku/portable-ascii.git",
-                "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
+                "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
-                "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
+                "url": "https://api.github.com/repos/voku/portable-ascii/zipball/d870a33f0f79d2b4579740b0620200221ee44aeb",
+                "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0.0"
+                "php": ">=7.1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0"
+                "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5"
             },
             "suggest": {
                 "ext-intl": "Use Intl for transliterator_transliterate() support"
@@ -7689,7 +7744,7 @@
             ],
             "support": {
                 "issues": "https://github.com/voku/portable-ascii/issues",
-                "source": "https://github.com/voku/portable-ascii/tree/2.0.3"
+                "source": "https://github.com/voku/portable-ascii/tree/2.1.0"
             },
             "funding": [
                 {
@@ -7713,7 +7768,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-11-21T01:49:47+00:00"
+            "time": "2026-04-16T23:10:39+00:00"
         },
         {
             "name": "webmozart/assert",
@@ -7772,21 +7827,84 @@
                 "source": "https://github.com/webmozarts/assert/tree/1.12.1"
             },
             "time": "2025-10-29T15:56:20+00:00"
+        },
+        {
+            "name": "wireui/heroicons",
+            "version": "v2.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/wireui/heroicons.git",
+                "reference": "77065cd36f53e2ae69d11717c10f5d1fc063816d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/wireui/heroicons/zipball/77065cd36f53e2ae69d11717c10f5d1fc063816d",
+                "reference": "77065cd36f53e2ae69d11717c10f5d1fc063816d",
+                "shasum": ""
+            },
+            "require": {
+                "laravel/framework": "^9.16|^10.0|^11.0|^12.0|^13.0",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "larastan/larastan": "^3.0",
+                "laravel/pint": "^1.6",
+                "orchestra/testbench": "^10.0",
+                "pestphp/pest": "^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "aliases": [],
+                "laravel": {
+                    "providers": [
+                        "WireUi\\Heroicons\\HeroiconsServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "WireUi\\Heroicons\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Pedro Oliveira",
+                    "email": "pedrolivertwd@gmail.com"
+                }
+            ],
+            "description": "The Tailwind Heroicons for laravel blade by WireUI",
+            "keywords": [
+                "blade components",
+                "blade heroicons",
+                "laravel components",
+                "livewire icons",
+                "livewire icons components",
+                "wireui"
+            ],
+            "support": {
+                "issues": "https://github.com/wireui/heroicons/issues",
+                "source": "https://github.com/wireui/heroicons/tree/v2.10.0"
+            },
+            "time": "2026-03-24T15:37:57+00:00"
         }
     ],
     "packages-dev": [
         {
             "name": "brianium/paratest",
-            "version": "v7.16.0",
+            "version": "v7.20.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/paratestphp/paratest.git",
-                "reference": "a10878ed0fe0bbc2f57c980f7a08065338b970b6"
+                "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/paratestphp/paratest/zipball/a10878ed0fe0bbc2f57c980f7a08065338b970b6",
-                "reference": "a10878ed0fe0bbc2f57c980f7a08065338b970b6",
+                "url": "https://api.github.com/repos/paratestphp/paratest/zipball/81c80677c9ec0ed4ef16b246167f11dec81a6e3d",
+                "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d",
                 "shasum": ""
             },
             "require": {
@@ -7797,24 +7915,24 @@
                 "fidry/cpu-core-counter": "^1.3.0",
                 "jean85/pretty-package-versions": "^2.1.1",
                 "php": "~8.3.0 || ~8.4.0 || ~8.5.0",
-                "phpunit/php-code-coverage": "^12.5.1",
-                "phpunit/php-file-iterator": "^6",
-                "phpunit/php-timer": "^8",
-                "phpunit/phpunit": "^12.5.2",
-                "sebastian/environment": "^8.0.3",
-                "symfony/console": "^7.3.4 || ^8.0.0",
-                "symfony/process": "^7.3.4 || ^8.0.0"
+                "phpunit/php-code-coverage": "^12.5.3 || ^13.0.1",
+                "phpunit/php-file-iterator": "^6.0.1 || ^7",
+                "phpunit/php-timer": "^8 || ^9",
+                "phpunit/phpunit": "^12.5.14 || ^13.0.5",
+                "sebastian/environment": "^8.0.3 || ^9",
+                "symfony/console": "^7.4.7 || ^8.0.7",
+                "symfony/process": "^7.4.5 || ^8.0.5"
             },
             "require-dev": {
                 "doctrine/coding-standard": "^14.0.0",
                 "ext-pcntl": "*",
                 "ext-pcov": "*",
                 "ext-posix": "*",
-                "phpstan/phpstan": "^2.1.33",
-                "phpstan/phpstan-deprecation-rules": "^2.0.3",
-                "phpstan/phpstan-phpunit": "^2.0.10",
-                "phpstan/phpstan-strict-rules": "^2.0.7",
-                "symfony/filesystem": "^7.3.2 || ^8.0.0"
+                "phpstan/phpstan": "^2.1.44",
+                "phpstan/phpstan-deprecation-rules": "^2.0.4",
+                "phpstan/phpstan-phpunit": "^2.0.16",
+                "phpstan/phpstan-strict-rules": "^2.0.10",
+                "symfony/filesystem": "^7.4.6 || ^8.0.6"
             },
             "bin": [
                 "bin/paratest",
@@ -7854,7 +7972,7 @@
             ],
             "support": {
                 "issues": "https://github.com/paratestphp/paratest/issues",
-                "source": "https://github.com/paratestphp/paratest/tree/v7.16.0"
+                "source": "https://github.com/paratestphp/paratest/tree/v7.20.0"
             },
             "funding": [
                 {
@@ -7866,7 +7984,7 @@
                     "type": "paypal"
                 }
             ],
-            "time": "2025-12-09T20:03:26+00:00"
+            "time": "2026-03-29T15:46:14+00:00"
         },
         {
             "name": "fakerphp/faker",
@@ -8063,6 +8181,108 @@
             ],
             "time": "2025-08-08T12:00:00+00:00"
         },
+        {
+            "name": "fruitcake/laravel-debugbar",
+            "version": "v4.2.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/fruitcake/laravel-debugbar.git",
+                "reference": "799d70c1101d3f8840dd76ff68ff6a78f9352905"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/799d70c1101d3f8840dd76ff68ff6a78f9352905",
+                "reference": "799d70c1101d3f8840dd76ff68ff6a78f9352905",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/routing": "^11|^12|^13.0",
+                "illuminate/session": "^11|^12|^13.0",
+                "illuminate/support": "^11|^12|^13.0",
+                "php": "^8.2",
+                "php-debugbar/php-debugbar": "^3.7.2",
+                "php-debugbar/symfony-bridge": "^1.1"
+            },
+            "replace": {
+                "barryvdh/laravel-debugbar": "self.version"
+            },
+            "require-dev": {
+                "larastan/larastan": "^3",
+                "laravel/octane": "^2",
+                "laravel/pennant": "^1",
+                "laravel/pint": "^1",
+                "laravel/telescope": "^5.16",
+                "livewire/livewire": "^3.7|^4",
+                "mockery/mockery": "^1.3.3",
+                "orchestra/testbench-dusk": "^9|^10|^11",
+                "php-debugbar/twig-bridge": "^2.0",
+                "phpstan/phpstan-phpunit": "^2",
+                "phpstan/phpstan-strict-rules": "^2.0",
+                "phpunit/phpunit": "^11",
+                "shipmonk/phpstan-rules": "^4.3"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "Debugbar": "Fruitcake\\LaravelDebugbar\\Facades\\Debugbar"
+                    },
+                    "providers": [
+                        "Fruitcake\\LaravelDebugbar\\ServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-master": "4.2-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Fruitcake\\LaravelDebugbar\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fruitcake",
+                    "homepage": "https://fruitcake.nl"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "PHP Debugbar integration for Laravel",
+            "keywords": [
+                "barryvdh",
+                "debug",
+                "debugbar",
+                "dev",
+                "laravel",
+                "profiler",
+                "webprofiler"
+            ],
+            "support": {
+                "issues": "https://github.com/fruitcake/laravel-debugbar/issues",
+                "source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.2.8"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-04-20T13:31:29+00:00"
+        },
         {
             "name": "hamcrest/hamcrest-php",
             "version": "v2.1.1",
@@ -8174,35 +8394,116 @@
             },
             "time": "2025-03-19T14:43:43+00:00"
         },
+        {
+            "name": "joshembling/artisan-browse",
+            "version": "v0.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/joshembling/artisan-browse.git",
+                "reference": "354415b6cc7700cde616749f87cb79446f816e64"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/joshembling/artisan-browse/zipball/354415b6cc7700cde616749f87cb79446f816e64",
+                "reference": "354415b6cc7700cde616749f87cb79446f816e64",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/contracts": "^11.0|^12.0|^13.0",
+                "laravel/prompts": "^0.3.0",
+                "php": "^8.3",
+                "spatie/laravel-package-tools": "^1.16"
+            },
+            "require-dev": {
+                "larastan/larastan": "^3.0",
+                "laravel/pint": "^1.14",
+                "nunomaduro/collision": "^8.8",
+                "orchestra/testbench": "^10.0.0||^9.0.0",
+                "pestphp/pest": "^4.0",
+                "pestphp/pest-plugin-arch": "^4.0",
+                "pestphp/pest-plugin-laravel": "^4.0",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan-deprecation-rules": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "ArtisanBrowse": "JoshEmbling\\ArtisanBrowse\\Facades\\ArtisanBrowse"
+                    },
+                    "providers": [
+                        "JoshEmbling\\ArtisanBrowse\\ArtisanBrowseServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "JoshEmbling\\ArtisanBrowse\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Josh Embling",
+                    "email": "joshembling@gmail.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An interactive terminal UI for browsing, searching, and executing Laravel Artisan commands.",
+            "homepage": "https://github.com/joshembling/artisan-browse",
+            "keywords": [
+                "Interactive CLI",
+                "artisan",
+                "artisan-browse",
+                "browse",
+                "joshembling",
+                "laravel",
+                "laravel commands",
+                "terminal UI"
+            ],
+            "support": {
+                "issues": "https://github.com/joshembling/artisan-browse/issues",
+                "source": "https://github.com/joshembling/artisan-browse/tree/v0.0.7"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/joshembling",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-03-17T10:51:29+00:00"
+        },
         {
             "name": "laradumps/laradumps",
-            "version": "v5.0.0",
+            "version": "v5.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laradumps/laradumps.git",
-                "reference": "7bfb9b888ce351ca151e29cacf2e2e74c7b62f72"
+                "reference": "22ac20bb88e960eb1f68535958adefbc963074ce"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laradumps/laradumps/zipball/7bfb9b888ce351ca151e29cacf2e2e74c7b62f72",
-                "reference": "7bfb9b888ce351ca151e29cacf2e2e74c7b62f72",
+                "url": "https://api.github.com/repos/laradumps/laradumps/zipball/22ac20bb88e960eb1f68535958adefbc963074ce",
+                "reference": "22ac20bb88e960eb1f68535958adefbc963074ce",
                 "shasum": ""
             },
             "require": {
-                "illuminate/mail": "^11.0|^12.0",
-                "illuminate/support": "^11.0|^12.0",
-                "laradumps/laradumps-core": "^4.0.0",
-                "nunomaduro/termwind": "^2.3.3",
+                "illuminate/mail": "^11.0 || ^12.0 || ^13.0",
+                "illuminate/support": "^11.0 || ^12.0 || ^13.0",
+                "laradumps/laradumps-core": "^4.0.3",
                 "php": "^8.2"
             },
             "require-dev": {
                 "larastan/larastan": "^3.8",
-                "laravel/framework": "^11.0|^12.0",
+                "laravel/framework": "^11.0 || ^12.0 || ^13.0",
                 "laravel/pint": "^1.26.0",
-                "livewire/livewire": "^3.7.1|^4.0",
                 "mockery/mockery": "^1.6.12",
-                "orchestra/testbench-core": "^9.4|^10.0",
-                "pestphp/pest": "^3.7.0|^4.0.0",
+                "orchestra/testbench-core": "^9.4 || ^10.0 || ^11.0",
+                "pestphp/pest": "^3.7.0 || ^4.0.0",
                 "symfony/var-dumper": "^7.1.3|^8.0"
             },
             "type": "library",
@@ -8236,7 +8537,7 @@
             "homepage": "https://github.com/laradumps/laradumps",
             "support": {
                 "issues": "https://github.com/laradumps/laradumps/issues",
-                "source": "https://github.com/laradumps/laradumps/tree/v5.0.0"
+                "source": "https://github.com/laradumps/laradumps/tree/v5.3.0"
             },
             "funding": [
                 {
@@ -8244,25 +8545,24 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-13T12:44:16+00:00"
+            "time": "2026-03-25T12:27:54+00:00"
         },
         {
             "name": "laradumps/laradumps-core",
-            "version": "v4.0.0",
+            "version": "v4.0.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laradumps/laradumps-core.git",
-                "reference": "bb57c8fccb785777020b85592d718e8ff0c9a23a"
+                "reference": "41acc4a0dba81232287bed4d98de112b1c466244"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laradumps/laradumps-core/zipball/bb57c8fccb785777020b85592d718e8ff0c9a23a",
-                "reference": "bb57c8fccb785777020b85592d718e8ff0c9a23a",
+                "url": "https://api.github.com/repos/laradumps/laradumps-core/zipball/41acc4a0dba81232287bed4d98de112b1c466244",
+                "reference": "41acc4a0dba81232287bed4d98de112b1c466244",
                 "shasum": ""
             },
             "require": {
                 "ext-curl": "*",
-                "nunomaduro/termwind": "^2.0",
                 "php": "^8.2",
                 "ramsey/uuid": "^4.9.1",
                 "spatie/backtrace": "^1.5",
@@ -8273,11 +8573,14 @@
                 "symfony/yaml": "^6.4|^7.0|^8.0"
             },
             "require-dev": {
-                "illuminate/support": "^10.46",
+                "illuminate/support": "^12",
                 "laravel/pint": "^1.26.0",
                 "pestphp/pest": "^3.0|^4.0",
                 "phpstan/phpstan": "^1.10.50"
             },
+            "suggest": {
+                "nunomaduro/termwind": "For a better terminal experience"
+            },
             "bin": [
                 "bin/laradumps"
             ],
@@ -8305,7 +8608,7 @@
             "homepage": "https://github.com/laradumps/laradumps-core",
             "support": {
                 "issues": "https://github.com/laradumps/laradumps-core/issues",
-                "source": "https://github.com/laradumps/laradumps-core/tree/v4.0.0"
+                "source": "https://github.com/laradumps/laradumps-core/tree/v4.0.6"
             },
             "funding": [
                 {
@@ -8313,37 +8616,37 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-12T22:10:38+00:00"
+            "time": "2026-03-19T15:05:22+00:00"
         },
         {
             "name": "laravel/boost",
-            "version": "v1.8.7",
+            "version": "v2.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/boost.git",
-                "reference": "7a5709a8134ed59d3e7f34fccbd74689830e296c"
+                "reference": "60386c7723ff7cb388b62b6c137597244a9cf2f2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/boost/zipball/7a5709a8134ed59d3e7f34fccbd74689830e296c",
-                "reference": "7a5709a8134ed59d3e7f34fccbd74689830e296c",
+                "url": "https://api.github.com/repos/laravel/boost/zipball/60386c7723ff7cb388b62b6c137597244a9cf2f2",
+                "reference": "60386c7723ff7cb388b62b6c137597244a9cf2f2",
                 "shasum": ""
             },
             "require": {
                 "guzzlehttp/guzzle": "^7.9",
-                "illuminate/console": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/support": "^10.49.0|^11.45.3|^12.41.1",
-                "laravel/mcp": "^0.5.1",
-                "laravel/prompts": "0.1.25|^0.3.6",
-                "laravel/roster": "^0.2.9",
-                "php": "^8.1"
+                "illuminate/console": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/contracts": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/routing": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/support": "^11.45.3|^12.41.1|^13.0",
+                "laravel/mcp": "^0.5.1|^0.6.0|^0.7.0",
+                "laravel/prompts": "^0.3.10",
+                "laravel/roster": "^0.5.0",
+                "php": "^8.2"
             },
             "require-dev": {
-                "laravel/pint": "^1.20.0",
+                "laravel/pint": "^1.27.0",
                 "mockery/mockery": "^1.6.12",
-                "orchestra/testbench": "^8.36.0|^9.15.0|^10.6",
+                "orchestra/testbench": "^9.15.0|^10.6|^11.0",
                 "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5",
                 "phpstan/phpstan": "^2.1.27",
                 "rector/rector": "^2.1"
@@ -8379,39 +8682,39 @@
                 "issues": "https://github.com/laravel/boost/issues",
                 "source": "https://github.com/laravel/boost"
             },
-            "time": "2025-12-19T15:04:12+00:00"
+            "time": "2026-04-22T13:29:20+00:00"
         },
         {
             "name": "laravel/mcp",
-            "version": "v0.5.1",
+            "version": "v0.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/mcp.git",
-                "reference": "10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4"
+                "reference": "3513b4feca5f1678be4d2261dcfa8e456436d02a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/mcp/zipball/10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4",
-                "reference": "10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4",
+                "url": "https://api.github.com/repos/laravel/mcp/zipball/3513b4feca5f1678be4d2261dcfa8e456436d02a",
+                "reference": "3513b4feca5f1678be4d2261dcfa8e456436d02a",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "ext-mbstring": "*",
-                "illuminate/console": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/container": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/http": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/json-schema": "^12.41.1",
-                "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/support": "^10.49.0|^11.45.3|^12.41.1",
-                "illuminate/validation": "^10.49.0|^11.45.3|^12.41.1",
-                "php": "^8.1"
+                "illuminate/console": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/container": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/contracts": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/http": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/json-schema": "^12.41.1|^13.0",
+                "illuminate/routing": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/support": "^11.45.3|^12.41.1|^13.0",
+                "illuminate/validation": "^11.45.3|^12.41.1|^13.0",
+                "php": "^8.2"
             },
             "require-dev": {
                 "laravel/pint": "^1.20",
-                "orchestra/testbench": "^8.36|^9.15|^10.8",
-                "pestphp/pest": "^2.36.0|^3.8.4|^4.1.0",
+                "orchestra/testbench": "^9.15|^10.8|^11.0",
+                "pestphp/pest": "^3.8.5|^4.3.2",
                 "phpstan/phpstan": "^2.1.27",
                 "rector/rector": "^2.2.4"
             },
@@ -8452,99 +8755,20 @@
                 "issues": "https://github.com/laravel/mcp/issues",
                 "source": "https://github.com/laravel/mcp"
             },
-            "time": "2025-12-17T06:14:23+00:00"
-        },
-        {
-            "name": "laravel/pail",
-            "version": "v1.2.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/laravel/pail.git",
-                "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/laravel/pail/zipball/49f92285ff5d6fc09816e976a004f8dec6a0ea30",
-                "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30",
-                "shasum": ""
-            },
-            "require": {
-                "ext-mbstring": "*",
-                "illuminate/console": "^10.24|^11.0|^12.0",
-                "illuminate/contracts": "^10.24|^11.0|^12.0",
-                "illuminate/log": "^10.24|^11.0|^12.0",
-                "illuminate/process": "^10.24|^11.0|^12.0",
-                "illuminate/support": "^10.24|^11.0|^12.0",
-                "nunomaduro/termwind": "^1.15|^2.0",
-                "php": "^8.2",
-                "symfony/console": "^6.0|^7.0"
-            },
-            "require-dev": {
-                "laravel/framework": "^10.24|^11.0|^12.0",
-                "laravel/pint": "^1.13",
-                "orchestra/testbench-core": "^8.13|^9.17|^10.8",
-                "pestphp/pest": "^2.20|^3.0|^4.0",
-                "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0",
-                "phpstan/phpstan": "^1.12.27",
-                "symfony/var-dumper": "^6.3|^7.0"
-            },
-            "type": "library",
-            "extra": {
-                "laravel": {
-                    "providers": [
-                        "Laravel\\Pail\\PailServiceProvider"
-                    ]
-                },
-                "branch-alias": {
-                    "dev-main": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Laravel\\Pail\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Taylor Otwell",
-                    "email": "taylor@laravel.com"
-                },
-                {
-                    "name": "Nuno Maduro",
-                    "email": "enunomaduro@gmail.com"
-                }
-            ],
-            "description": "Easily delve into your Laravel application's log files directly from the command line.",
-            "homepage": "https://github.com/laravel/pail",
-            "keywords": [
-                "dev",
-                "laravel",
-                "logs",
-                "php",
-                "tail"
-            ],
-            "support": {
-                "issues": "https://github.com/laravel/pail/issues",
-                "source": "https://github.com/laravel/pail"
-            },
-            "time": "2025-11-20T16:29:35+00:00"
+            "time": "2026-04-21T10:23:03+00:00"
         },
         {
             "name": "laravel/pint",
-            "version": "v1.26.0",
+            "version": "v1.29.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/pint.git",
-                "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f"
+                "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f",
-                "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f",
+                "url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80",
+                "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80",
                 "shasum": ""
             },
             "require": {
@@ -8555,13 +8779,14 @@
                 "php": "^8.2.0"
             },
             "require-dev": {
-                "friendsofphp/php-cs-fixer": "^3.90.0",
-                "illuminate/view": "^12.40.1",
-                "larastan/larastan": "^3.8.0",
-                "laravel-zero/framework": "^12.0.4",
+                "friendsofphp/php-cs-fixer": "^3.95.1",
+                "illuminate/view": "^12.56.0",
+                "larastan/larastan": "^3.9.6",
+                "laravel-zero/framework": "^12.1.0",
                 "mockery/mockery": "^1.6.12",
-                "nunomaduro/termwind": "^2.3.3",
-                "pestphp/pest": "^3.8.4"
+                "nunomaduro/termwind": "^2.4.0",
+                "pestphp/pest": "^3.8.6",
+                "shipfastlabs/agent-detector": "^1.1.3"
             },
             "bin": [
                 "builds/pint"
@@ -8598,35 +8823,35 @@
                 "issues": "https://github.com/laravel/pint/issues",
                 "source": "https://github.com/laravel/pint"
             },
-            "time": "2025-11-25T21:15:52+00:00"
+            "time": "2026-04-20T15:26:14+00:00"
         },
         {
             "name": "laravel/roster",
-            "version": "v0.2.9",
+            "version": "v0.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/roster.git",
-                "reference": "82bbd0e2de614906811aebdf16b4305956816fa6"
+                "reference": "5089de7615f72f78e831590ff9d0435fed0102bb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/roster/zipball/82bbd0e2de614906811aebdf16b4305956816fa6",
-                "reference": "82bbd0e2de614906811aebdf16b4305956816fa6",
+                "url": "https://api.github.com/repos/laravel/roster/zipball/5089de7615f72f78e831590ff9d0435fed0102bb",
+                "reference": "5089de7615f72f78e831590ff9d0435fed0102bb",
                 "shasum": ""
             },
             "require": {
-                "illuminate/console": "^10.0|^11.0|^12.0",
-                "illuminate/contracts": "^10.0|^11.0|^12.0",
-                "illuminate/routing": "^10.0|^11.0|^12.0",
-                "illuminate/support": "^10.0|^11.0|^12.0",
-                "php": "^8.1|^8.2",
-                "symfony/yaml": "^6.4|^7.2"
+                "illuminate/console": "^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^11.0|^12.0|^13.0",
+                "illuminate/routing": "^11.0|^12.0|^13.0",
+                "illuminate/support": "^11.0|^12.0|^13.0",
+                "php": "^8.2",
+                "symfony/yaml": "^7.2|^8.0"
             },
             "require-dev": {
                 "laravel/pint": "^1.14",
                 "mockery/mockery": "^1.6",
-                "orchestra/testbench": "^8.22.0|^9.0|^10.0",
-                "pestphp/pest": "^2.0|^3.0",
+                "orchestra/testbench": "^9.0|^10.0|^11.0",
+                "pestphp/pest": "^3.0|^4.1",
                 "phpstan/phpstan": "^2.0"
             },
             "type": "library",
@@ -8659,32 +8884,32 @@
                 "issues": "https://github.com/laravel/roster/issues",
                 "source": "https://github.com/laravel/roster"
             },
-            "time": "2025-10-20T09:56:46+00:00"
+            "time": "2026-03-05T07:58:43+00:00"
         },
         {
             "name": "laravel/sail",
-            "version": "v1.51.0",
+            "version": "v1.57.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/sail.git",
-                "reference": "1c74357df034e869250b4365dd445c9f6ba5d068"
+                "reference": "fa8d057b6e9310380ccbc3a209ed7f927d54f648"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/sail/zipball/1c74357df034e869250b4365dd445c9f6ba5d068",
-                "reference": "1c74357df034e869250b4365dd445c9f6ba5d068",
+                "url": "https://api.github.com/repos/laravel/sail/zipball/fa8d057b6e9310380ccbc3a209ed7f927d54f648",
+                "reference": "fa8d057b6e9310380ccbc3a209ed7f927d54f648",
                 "shasum": ""
             },
             "require": {
-                "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0",
-                "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0",
-                "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0",
+                "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
+                "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0",
                 "php": "^8.0",
-                "symfony/console": "^6.0|^7.0",
-                "symfony/yaml": "^6.0|^7.0"
+                "symfony/console": "^6.0|^7.0|^8.0",
+                "symfony/yaml": "^6.0|^7.0|^8.0"
             },
             "require-dev": {
-                "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
+                "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0",
                 "phpstan/phpstan": "^2.0"
             },
             "bin": [
@@ -8722,7 +8947,7 @@
                 "issues": "https://github.com/laravel/sail/issues",
                 "source": "https://github.com/laravel/sail"
             },
-            "time": "2025-12-09T13:33:49+00:00"
+            "time": "2026-04-14T13:32:04+00:00"
         },
         {
             "name": "mockery/mockery",
@@ -8869,39 +9094,36 @@
         },
         {
             "name": "nunomaduro/collision",
-            "version": "v8.8.3",
+            "version": "v8.9.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nunomaduro/collision.git",
-                "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4"
+                "reference": "716af8f95a470e9094cfca09ed897b023be191a5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
-                "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
+                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5",
+                "reference": "716af8f95a470e9094cfca09ed897b023be191a5",
                 "shasum": ""
             },
             "require": {
-                "filp/whoops": "^2.18.1",
-                "nunomaduro/termwind": "^2.3.1",
+                "filp/whoops": "^2.18.4",
+                "nunomaduro/termwind": "^2.4.0",
                 "php": "^8.2.0",
-                "symfony/console": "^7.3.0"
+                "symfony/console": "^7.4.8 || ^8.0.8"
             },
             "conflict": {
-                "laravel/framework": "<11.44.2 || >=13.0.0",
-                "phpunit/phpunit": "<11.5.15 || >=13.0.0"
+                "laravel/framework": "<11.48.0 || >=14.0.0",
+                "phpunit/phpunit": "<11.5.50 || >=14.0.0"
             },
             "require-dev": {
-                "brianium/paratest": "^7.8.3",
-                "larastan/larastan": "^3.4.2",
-                "laravel/framework": "^11.44.2 || ^12.18",
-                "laravel/pint": "^1.22.1",
-                "laravel/sail": "^1.43.1",
-                "laravel/sanctum": "^4.1.1",
-                "laravel/tinker": "^2.10.1",
-                "orchestra/testbench-core": "^9.12.0 || ^10.4",
-                "pestphp/pest": "^3.8.2 || ^4.0.0",
-                "sebastian/environment": "^7.2.1 || ^8.0"
+                "brianium/paratest": "^7.8.5",
+                "larastan/larastan": "^3.9.6",
+                "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0",
+                "laravel/pint": "^1.29.1",
+                "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1",
+                "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0",
+                "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0"
             },
             "type": "library",
             "extra": {
@@ -8964,45 +9186,46 @@
                     "type": "patreon"
                 }
             ],
-            "time": "2025-11-20T02:55:25+00:00"
+            "time": "2026-04-21T14:04:20+00:00"
         },
         {
             "name": "pestphp/pest",
-            "version": "v4.3.0",
+            "version": "v4.6.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pestphp/pest.git",
-                "reference": "e86bec3e68f1874c112ca782fb9db1333f3fe7ab"
+                "reference": "bff44562a99d30aa37573995566051b0344f9f8e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pestphp/pest/zipball/e86bec3e68f1874c112ca782fb9db1333f3fe7ab",
-                "reference": "e86bec3e68f1874c112ca782fb9db1333f3fe7ab",
+                "url": "https://api.github.com/repos/pestphp/pest/zipball/bff44562a99d30aa37573995566051b0344f9f8e",
+                "reference": "bff44562a99d30aa37573995566051b0344f9f8e",
                 "shasum": ""
             },
             "require": {
-                "brianium/paratest": "^7.16.0",
-                "nunomaduro/collision": "^8.8.3",
-                "nunomaduro/termwind": "^2.3.3",
+                "brianium/paratest": "^7.20.0",
+                "nunomaduro/collision": "^8.9.3",
+                "nunomaduro/termwind": "^2.4.0",
                 "pestphp/pest-plugin": "^4.0.0",
-                "pestphp/pest-plugin-arch": "^4.0.0",
+                "pestphp/pest-plugin-arch": "^4.0.2",
                 "pestphp/pest-plugin-mutate": "^4.0.1",
                 "pestphp/pest-plugin-profanity": "^4.2.1",
                 "php": "^8.3.0",
-                "phpunit/phpunit": "^12.5.4",
-                "symfony/process": "^7.4.0|^8.0.0"
+                "phpunit/phpunit": "^12.5.23",
+                "symfony/process": "^7.4.8|^8.0.8"
             },
             "conflict": {
                 "filp/whoops": "<2.18.3",
-                "phpunit/phpunit": ">12.5.4",
+                "phpunit/phpunit": ">12.5.23",
                 "sebastian/exporter": "<7.0.0",
                 "webmozart/assert": "<1.11.0"
             },
             "require-dev": {
-                "pestphp/pest-dev-tools": "^4.0.0",
-                "pestphp/pest-plugin-browser": "^4.1.1",
-                "pestphp/pest-plugin-type-coverage": "^4.0.3",
-                "psy/psysh": "^0.12.18"
+                "mrpunyapal/peststan": "^0.2.5",
+                "pestphp/pest-dev-tools": "^4.1.0",
+                "pestphp/pest-plugin-browser": "^4.3.1",
+                "pestphp/pest-plugin-type-coverage": "^4.0.4",
+                "psy/psysh": "^0.12.22"
             },
             "bin": [
                 "bin/pest"
@@ -9068,7 +9291,7 @@
             ],
             "support": {
                 "issues": "https://github.com/pestphp/pest/issues",
-                "source": "https://github.com/pestphp/pest/tree/v4.3.0"
+                "source": "https://github.com/pestphp/pest/tree/v4.6.3"
             },
             "funding": [
                 {
@@ -9080,7 +9303,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-12-30T19:48:33+00:00"
+            "time": "2026-04-18T13:51:25+00:00"
         },
         {
             "name": "pestphp/pest-plugin",
@@ -9154,26 +9377,26 @@
         },
         {
             "name": "pestphp/pest-plugin-arch",
-            "version": "v4.0.0",
+            "version": "v4.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pestphp/pest-plugin-arch.git",
-                "reference": "25bb17e37920ccc35cbbcda3b00d596aadf3e58d"
+                "reference": "3fb0d02a91b9da504b139dc7ab2a31efb7c3215c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/25bb17e37920ccc35cbbcda3b00d596aadf3e58d",
-                "reference": "25bb17e37920ccc35cbbcda3b00d596aadf3e58d",
+                "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/3fb0d02a91b9da504b139dc7ab2a31efb7c3215c",
+                "reference": "3fb0d02a91b9da504b139dc7ab2a31efb7c3215c",
                 "shasum": ""
             },
             "require": {
                 "pestphp/pest-plugin": "^4.0.0",
                 "php": "^8.3",
-                "ta-tikoma/phpunit-architecture-test": "^0.8.5"
+                "ta-tikoma/phpunit-architecture-test": "^0.8.7"
             },
             "require-dev": {
-                "pestphp/pest": "^4.0.0",
-                "pestphp/pest-dev-tools": "^4.0.0"
+                "pestphp/pest": "^4.4.6",
+                "pestphp/pest-dev-tools": "^4.1.0"
             },
             "type": "library",
             "extra": {
@@ -9208,7 +9431,7 @@
                 "unit"
             ],
             "support": {
-                "source": "https://github.com/pestphp/pest-plugin-arch/tree/v4.0.0"
+                "source": "https://github.com/pestphp/pest-plugin-arch/tree/v4.0.2"
             },
             "funding": [
                 {
@@ -9220,31 +9443,31 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-08-20T13:10:51+00:00"
+            "time": "2026-04-10T17:20:19+00:00"
         },
         {
             "name": "pestphp/pest-plugin-laravel",
-            "version": "v4.0.0",
+            "version": "v4.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pestphp/pest-plugin-laravel.git",
-                "reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e"
+                "reference": "3057a36669ff11416cc0dc2b521b3aec58c488d0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/e12a07046b826a40b1c8632fd7b80d6b8d7b628e",
-                "reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e",
+                "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/3057a36669ff11416cc0dc2b521b3aec58c488d0",
+                "reference": "3057a36669ff11416cc0dc2b521b3aec58c488d0",
                 "shasum": ""
             },
             "require": {
-                "laravel/framework": "^11.45.2|^12.25.0",
-                "pestphp/pest": "^4.0.0",
+                "laravel/framework": "^11.45.2|^12.52.0|^13.0",
+                "pestphp/pest": "^4.4.1",
                 "php": "^8.3.0"
             },
             "require-dev": {
-                "laravel/dusk": "^8.3.3",
-                "orchestra/testbench": "^9.13.0|^10.5.0",
-                "pestphp/pest-dev-tools": "^4.0.0"
+                "laravel/dusk": "^8.3.6",
+                "orchestra/testbench": "^9.13.0|^10.9.0|^11.0",
+                "pestphp/pest-dev-tools": "^4.1.0"
             },
             "type": "library",
             "extra": {
@@ -9282,7 +9505,7 @@
                 "unit"
             ],
             "support": {
-                "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v4.0.0"
+                "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v4.1.0"
             },
             "funding": [
                 {
@@ -9294,7 +9517,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-08-20T12:46:37+00:00"
+            "time": "2026-02-21T00:29:45+00:00"
         },
         {
             "name": "pestphp/pest-plugin-mutate",
@@ -9546,18 +9769,186 @@
             },
             "time": "2022-02-21T01:04:05+00:00"
         },
+        {
+            "name": "php-debugbar/php-debugbar",
+            "version": "v3.7.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-debugbar/php-debugbar.git",
+                "reference": "dbf77f48fa6e6b57ed57ae67aa047b2535697788"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/dbf77f48fa6e6b57ed57ae67aa047b2535697788",
+                "reference": "dbf77f48fa6e6b57ed57ae67aa047b2535697788",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.2",
+                "psr/log": "^1|^2|^3",
+                "symfony/var-dumper": "^5.4|^6|^7|^8"
+            },
+            "replace": {
+                "maximebf/debugbar": "self.version"
+            },
+            "require-dev": {
+                "dbrekelmans/bdi": "^1.4",
+                "friendsofphp/php-cs-fixer": "^3.92",
+                "monolog/monolog": "^3.9",
+                "php-debugbar/doctrine-bridge": "^3@dev",
+                "php-debugbar/monolog-bridge": "^1@dev",
+                "php-debugbar/symfony-bridge": "^1@dev",
+                "php-debugbar/twig-bridge": "^2@dev",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpstan/phpstan-strict-rules": "^2.0",
+                "phpunit/phpunit": "^10",
+                "predis/predis": "^3.3",
+                "shipmonk/phpstan-rules": "^4.3",
+                "symfony/browser-kit": "^6.4|7.0",
+                "symfony/dom-crawler": "^6.4|^7",
+                "symfony/event-dispatcher": "^5.4|^6.4|^7.3|^8.0",
+                "symfony/http-foundation": "^5.4|^6.4|^7.3|^8.0",
+                "symfony/mailer": "^5.4|^6.4|^7.3|^8.0",
+                "symfony/panther": "^1|^2.1",
+                "twig/twig": "^3.11.2"
+            },
+            "suggest": {
+                "php-debugbar/doctrine-bridge": "To integrate Doctrine with php-debugbar.",
+                "php-debugbar/monolog-bridge": "To integrate Monolog with php-debugbar.",
+                "php-debugbar/symfony-bridge": "To integrate Symfony with php-debugbar.",
+                "php-debugbar/twig-bridge": "To integrate Twig with php-debugbar."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DebugBar\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Bouroumeau-Fuseau",
+                    "email": "maxime.bouroumeau@gmail.com",
+                    "homepage": "http://maximebf.com"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Debug bar in the browser for php application",
+            "homepage": "https://github.com/php-debugbar/php-debugbar",
+            "keywords": [
+                "debug",
+                "debug bar",
+                "debugbar",
+                "dev",
+                "profiler",
+                "toolbar"
+            ],
+            "support": {
+                "issues": "https://github.com/php-debugbar/php-debugbar/issues",
+                "source": "https://github.com/php-debugbar/php-debugbar/tree/v3.7.5"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-04-15T11:58:43+00:00"
+        },
+        {
+            "name": "php-debugbar/symfony-bridge",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-debugbar/symfony-bridge.git",
+                "reference": "e37d2debe5d316408b00d0ab2688d9c2cf59b5ad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-debugbar/symfony-bridge/zipball/e37d2debe5d316408b00d0ab2688d9c2cf59b5ad",
+                "reference": "e37d2debe5d316408b00d0ab2688d9c2cf59b5ad",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.2",
+                "php-debugbar/php-debugbar": "^3.1",
+                "symfony/http-foundation": "^5.4|^6.4|^7.3|^8.0"
+            },
+            "require-dev": {
+                "dbrekelmans/bdi": "^1.4",
+                "phpunit/phpunit": "^10",
+                "symfony/browser-kit": "^6|^7",
+                "symfony/dom-crawler": "^6|^7",
+                "symfony/mailer": "^5.4|^6.4|^7.3|^8.0",
+                "symfony/panther": "^1|^2.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DebugBar\\Bridge\\Symfony\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Bouroumeau-Fuseau",
+                    "email": "maxime.bouroumeau@gmail.com",
+                    "homepage": "http://maximebf.com"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Symfony bridge for PHP Debugbar",
+            "homepage": "https://github.com/php-debugbar/php-debugbar",
+            "keywords": [
+                "debugbar",
+                "dev",
+                "symfony"
+            ],
+            "support": {
+                "issues": "https://github.com/php-debugbar/symfony-bridge/issues",
+                "source": "https://github.com/php-debugbar/symfony-bridge/tree/v1.1.0"
+            },
+            "time": "2026-01-15T14:47:34+00:00"
+        },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "12.5.2",
+            "version": "12.5.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b"
+                "reference": "876099a072646c7745f673d7aeab5382c4439691"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b",
-                "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/876099a072646c7745f673d7aeab5382c4439691",
+                "reference": "876099a072646c7745f673d7aeab5382c4439691",
                 "shasum": ""
             },
             "require": {
@@ -9566,7 +9957,6 @@
                 "ext-xmlwriter": "*",
                 "nikic/php-parser": "^5.7.0",
                 "php": ">=8.3",
-                "phpunit/php-file-iterator": "^6.0",
                 "phpunit/php-text-template": "^5.0",
                 "sebastian/complexity": "^5.0",
                 "sebastian/environment": "^8.0.3",
@@ -9613,7 +10003,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
                 "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2"
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.6"
             },
             "funding": [
                 {
@@ -9633,20 +10023,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-12-24T07:03:04+00:00"
+            "time": "2026-04-15T08:23:17+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "6.0.0",
+            "version": "6.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "961bc913d42fe24a257bfff826a5068079ac7782"
+                "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782",
-                "reference": "961bc913d42fe24a257bfff826a5068079ac7782",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5",
+                "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5",
                 "shasum": ""
             },
             "require": {
@@ -9686,15 +10076,27 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
                 "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
-                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0"
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1"
             },
             "funding": [
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2025-02-07T04:58:37+00:00"
+            "time": "2026-02-02T14:04:18+00:00"
         },
         {
             "name": "phpunit/php-invoker",
@@ -9882,16 +10284,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "12.5.4",
+            "version": "12.5.23",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a"
+                "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4ba0e923f9d3fc655de22f9547c01d15a41fc93a",
-                "reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969",
+                "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969",
                 "shasum": ""
             },
             "require": {
@@ -9905,18 +10307,19 @@
                 "phar-io/manifest": "^2.0.4",
                 "phar-io/version": "^3.2.1",
                 "php": ">=8.3",
-                "phpunit/php-code-coverage": "^12.5.1",
-                "phpunit/php-file-iterator": "^6.0.0",
+                "phpunit/php-code-coverage": "^12.5.6",
+                "phpunit/php-file-iterator": "^6.0.1",
                 "phpunit/php-invoker": "^6.0.0",
                 "phpunit/php-text-template": "^5.0.0",
                 "phpunit/php-timer": "^8.0.0",
                 "sebastian/cli-parser": "^4.2.0",
-                "sebastian/comparator": "^7.1.3",
+                "sebastian/comparator": "^7.1.6",
                 "sebastian/diff": "^7.0.0",
-                "sebastian/environment": "^8.0.3",
+                "sebastian/environment": "^8.1.0",
                 "sebastian/exporter": "^7.0.2",
                 "sebastian/global-state": "^8.0.2",
                 "sebastian/object-enumerator": "^7.0.0",
+                "sebastian/recursion-context": "^7.0.1",
                 "sebastian/type": "^6.0.3",
                 "sebastian/version": "^6.0.0",
                 "staabm/side-effects-detector": "^1.0.5"
@@ -9959,31 +10362,15 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.4"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.23"
             },
             "funding": [
                 {
-                    "url": "https://phpunit.de/sponsors.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                },
-                {
-                    "url": "https://liberapay.com/sebastianbergmann",
-                    "type": "liberapay"
-                },
-                {
-                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
-                    "type": "thanks_dev"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
-                    "type": "tidelift"
+                    "url": "https://phpunit.de/sponsoring.html",
+                    "type": "other"
                 }
             ],
-            "time": "2025-12-15T06:05:34+00:00"
+            "time": "2026-04-18T06:12:49+00:00"
         },
         {
             "name": "sebastian/cli-parser",
@@ -10056,16 +10443,16 @@
         },
         {
             "name": "sebastian/comparator",
-            "version": "7.1.3",
+            "version": "7.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148"
+                "reference": "c769009dee98f494e0edc3fd4f4087501688f11e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148",
-                "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c769009dee98f494e0edc3fd4f4087501688f11e",
+                "reference": "c769009dee98f494e0edc3fd4f4087501688f11e",
                 "shasum": ""
             },
             "require": {
@@ -10124,7 +10511,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
                 "security": "https://github.com/sebastianbergmann/comparator/security/policy",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3"
+                "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.6"
             },
             "funding": [
                 {
@@ -10144,7 +10531,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-08-20T11:27:00+00:00"
+            "time": "2026-04-14T08:23:15+00:00"
         },
         {
             "name": "sebastian/complexity",
@@ -10273,16 +10660,16 @@
         },
         {
             "name": "sebastian/environment",
-            "version": "8.0.3",
+            "version": "8.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
+                "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
-                "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b121608b28a13f721e76ffbbd386d08eff58f3f6",
+                "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6",
                 "shasum": ""
             },
             "require": {
@@ -10297,7 +10684,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "8.0-dev"
+                    "dev-main": "8.1-dev"
                 }
             },
             "autoload": {
@@ -10325,7 +10712,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/environment/issues",
                 "security": "https://github.com/sebastianbergmann/environment/security/policy",
-                "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
+                "source": "https://github.com/sebastianbergmann/environment/tree/8.1.0"
             },
             "funding": [
                 {
@@ -10345,7 +10732,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-08-12T14:11:56+00:00"
+            "time": "2026-04-15T12:13:01+00:00"
         },
         {
             "name": "sebastian/exporter",
@@ -10884,16 +11271,16 @@
         },
         {
             "name": "spatie/backtrace",
-            "version": "1.8.1",
+            "version": "1.8.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/spatie/backtrace.git",
-                "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
+                "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
-                "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
+                "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc",
+                "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc",
                 "shasum": ""
             },
             "require": {
@@ -10904,7 +11291,7 @@
                 "laravel/serializable-closure": "^1.3 || ^2.0",
                 "phpunit/phpunit": "^9.3 || ^11.4.3",
                 "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
-                "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0"
+                "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0"
             },
             "type": "library",
             "autoload": {
@@ -10932,7 +11319,7 @@
             ],
             "support": {
                 "issues": "https://github.com/spatie/backtrace/issues",
-                "source": "https://github.com/spatie/backtrace/tree/1.8.1"
+                "source": "https://github.com/spatie/backtrace/tree/1.8.2"
             },
             "funding": [
                 {
@@ -10944,7 +11331,7 @@
                     "type": "other"
                 }
             ],
-            "time": "2025-08-26T08:22:30+00:00"
+            "time": "2026-03-11T13:48:28+00:00"
         },
         {
             "name": "staabm/side-effects-detector",
@@ -11000,24 +11387,24 @@
         },
         {
             "name": "ta-tikoma/phpunit-architecture-test",
-            "version": "0.8.5",
+            "version": "0.8.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git",
-                "reference": "cf6fb197b676ba716837c886baca842e4db29005"
+                "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/cf6fb197b676ba716837c886baca842e4db29005",
-                "reference": "cf6fb197b676ba716837c886baca842e4db29005",
+                "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8",
+                "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8",
                 "shasum": ""
             },
             "require": {
                 "nikic/php-parser": "^4.18.0 || ^5.0.0",
                 "php": "^8.1.0",
-                "phpdocumentor/reflection-docblock": "^5.3.0",
-                "phpunit/phpunit": "^10.5.5  || ^11.0.0 || ^12.0.0",
-                "symfony/finder": "^6.4.0 || ^7.0.0"
+                "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0",
+                "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0",
+                "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0"
             },
             "require-dev": {
                 "laravel/pint": "^1.13.7",
@@ -11053,9 +11440,9 @@
             ],
             "support": {
                 "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues",
-                "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.5"
+                "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7"
             },
-            "time": "2025-04-20T20:23:40+00:00"
+            "time": "2026-02-17T17:25:14+00:00"
         },
         {
             "name": "theseer/tokenizer",
@@ -11114,7 +11501,7 @@
     "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {
-        "php": "^8.2"
+        "php": "^8.4"
     },
     "platform-dev": {},
     "plugin-api-version": "2.9.0"
diff --git a/config/app.php b/config/app.php
index 423eed5..80fb5eb 100644
--- a/config/app.php
+++ b/config/app.php
@@ -123,4 +123,4 @@
         'store' => env('APP_MAINTENANCE_STORE', 'database'),
     ],
 
-];
+];
\ No newline at end of file
diff --git a/config/artisan-browse.php b/config/artisan-browse.php
new file mode 100644
index 0000000..5e0eb5b
--- /dev/null
+++ b/config/artisan-browse.php
@@ -0,0 +1,27 @@
+ [
+        // Example: 'horizon', 'octane', 'sail'
+    ],
+
+    // Number of artisan commands to show before enabling scroll in the command selection UI.
+    'select_command_scroll' => 50,
+
+    // Number of options to show before enabling scroll in the argument/option selection UI.
+    'select_options_scroll' => 20,
+
+    // Options/arguments to skip when collecting input for a command.
+    'skip_options' => ['help', 'quiet', 'verbose', 'version', 'ansi', 'no-ansi', 'no-interaction', 'env', 'silent'],
+
+    // Whether to show the command preview before confirming
+    'show_command_preview' => true,
+
+    // Whether to search against command descriptions
+    'search_descriptions' => true,
+
+    // Whether to automatically execute the command without showing the confirmation prompt
+    'auto_execute' => false,
+];
diff --git a/config/fortify.php b/config/fortify.php
deleted file mode 100644
index e29363f..0000000
--- a/config/fortify.php
+++ /dev/null
@@ -1,159 +0,0 @@
- 'web',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Fortify Password Broker
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify which password broker Fortify can use when a user
-    | is resetting their password. This configured value should match one
-    | of your password brokers setup in your "auth" configuration file.
-    |
-    */
-
-    'passwords' => 'users',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Username / Email
-    |--------------------------------------------------------------------------
-    |
-    | This value defines which model attribute should be considered as your
-    | application's "username" field. Typically, this might be the email
-    | address of the users but you are free to change this value here.
-    |
-    | Out of the box, Fortify expects forgot password and reset password
-    | requests to have a field named 'email'. If the application uses
-    | another name for the field you may define it below as needed.
-    |
-    */
-
-    'username' => 'email',
-
-    'email' => 'email',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Lowercase Usernames
-    |--------------------------------------------------------------------------
-    |
-    | This value defines whether usernames should be lowercased before saving
-    | them in the database, as some database system string fields are case
-    | sensitive. You may disable this for your application if necessary.
-    |
-    */
-
-    'lowercase_usernames' => true,
-
-    /*
-    |--------------------------------------------------------------------------
-    | Home Path
-    |--------------------------------------------------------------------------
-    |
-    | Here you may configure the path where users will get redirected during
-    | authentication or password reset when the operations are successful
-    | and the user is authenticated. You are free to change this value.
-    |
-    */
-
-    'home' => '/dashboard',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Fortify Routes Prefix / Subdomain
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify which prefix Fortify will assign to all the routes
-    | that it registers with the application. If necessary, you may change
-    | subdomain under which all of the Fortify routes will be available.
-    |
-    */
-
-    'prefix' => '',
-
-    'domain' => null,
-
-    /*
-    |--------------------------------------------------------------------------
-    | Fortify Routes Middleware
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify which middleware Fortify will assign to the routes
-    | that it registers with the application. If necessary, you may change
-    | these middleware but typically this provided default is preferred.
-    |
-    */
-
-    'middleware' => ['web'],
-
-    /*
-    |--------------------------------------------------------------------------
-    | Rate Limiting
-    |--------------------------------------------------------------------------
-    |
-    | By default, Fortify will throttle logins to five requests per minute for
-    | every email and IP address combination. However, if you would like to
-    | specify a custom rate limiter to call then you may specify it here.
-    |
-    */
-
-    'limiters' => [
-        'login' => 'login',
-        'two-factor' => 'two-factor',
-    ],
-
-    /*
-    |--------------------------------------------------------------------------
-    | Register View Routes
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify if the routes returning views should be disabled as
-    | you may not need them when building your own application. This may be
-    | especially true if you're writing a custom single-page application.
-    |
-    */
-
-    'views' => true,
-
-    /*
-    |--------------------------------------------------------------------------
-    | Features
-    |--------------------------------------------------------------------------
-    |
-    | Some of the Fortify features are optional. You may disable the features
-    | by removing them from this array. You're free to only remove some of
-    | these features or you can even remove all of these if you need to.
-    |
-    */
-
-    'features' => [
-        Features::registration(),
-        Features::resetPasswords(),
-        Features::emailVerification(),
-        // Features::updateProfileInformation(),
-        // Features::updatePasswords(),
-        Features::twoFactorAuthentication([
-            'confirm' => true,
-            'confirmPassword' => true,
-            // 'window' => 0,
-        ]),
-    ],
-
-];
diff --git a/config/myapp.php b/config/myapp.php
new file mode 100644
index 0000000..6f8df20
--- /dev/null
+++ b/config/myapp.php
@@ -0,0 +1,51 @@
+ env('MY_PROJECT_SLUG'),
+    'importDir' => env('MY_IMPORT_DIR'),
+    'imagePlaceholder' => env('MY_IMAGE_PLACEHOLDER'),
+    'cacheDuration' => (int) env('MY_CACHE_DURATION', 86400),
+    'image' => [
+        'domain' => env('MY_IMAGE_DOMAIN'),
+        'featured' => 'featured.jpg',
+        'cover' => 'cover.jpg',
+        'svgPath' => 'app/public/images/svg',
+        'placeholder' => [
+            'page' => 'storage/images/placeholders/page.jpg',
+            'article' => 'storage/images/placeholders/article.jpg',
+            'place' => 'storage/images/placeholders/place.webp',
+        ],
+    ],
+    'album' => [
+        'default' => 'ivnbg',
+        'storageDir' => 'app/public/albums',
+        'dir' => [
+            'source' => storage_path() . '/app/public/albums',
+            'target' => storage_path() . '/app/public/albums',
+        ],
+        'file' => [
+            'albums' => 'albums.json',
+            'events' => 'events.json',
+            'images' => 'images.json',
+
+        ],
+        'url' => env('MY_ALBUM_URL'),
+        'srcDir' => 'src',
+        'thumbDir' => 'thumb',
+        'thumbWidth' => 200,
+        'featured' => 'featured.jpg',
+        'cover' => 'cover.jpg',
+    ],
+    // Project-specific settings
+    'name' => '',
+    'logo' => '',
+    'slogan' => '',
+    'searchInContentType' =>[],
+    'metaTitle' => '',
+    'metaDescription' => '',
+    'metaKeywords' => '',
+    'gatMeasurementId' => null,
+    'headerNav' => [],
+    'footerNav' => [ ],
+
+];
diff --git a/config/projects/ivnbg.php b/config/projects/ivnbg.php
new file mode 100644
index 0000000..e8c0447
--- /dev/null
+++ b/config/projects/ivnbg.php
@@ -0,0 +1,88 @@
+ [
+        'name' => 'home',
+        'label' => 'home',
+        'uri' => 'home',
+        'isExternal' => false,
+    ],
+    'articleIndex' => [
+        'name' => 'articleIndex',
+        'label' => 'app.nav.articleIndex',
+        'uri' => 'blog',
+        'isExternal' => false,
+    ],
+    'tagArticle' => [
+        'name' => 'tagArticle',
+        'label' => 'app.nav.tags',
+        'uri' => 'blog/tags',
+        'isExternal' => false,
+    ],
+    'placeIndex' => [
+        'name' => 'placeIndex',
+        'label' => 'app.nav.placeIndex',
+        'uri' => 'places',
+        'isExternal' => false,
+
+    ],
+    'photoGalleryIndex' => [
+        'name' => 'photoGalleryIndex',
+        'label' => 'app.nav.photoGallery',
+        'uri' => 'photo-gallery',
+        'isExternal' => false,
+    ],
+    'contact' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.contact',
+        'hasIcon' => 'contact',
+        'uri' => 'contact',
+        'isExternal' => false,
+        'params' => ['slug' => 'contact'],
+    ],
+    'about' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.about',
+        'hasIcon' => 'info-circle',
+        'uri' => 'about',
+        'isExternal' => false,
+        'params' => ['slug' => 'about'],
+    ],
+    'termsAndConditions' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.termsAndConditions',
+        'hasIcon' => 'info-circle',
+        'uri' => 'terms-conditions',
+        'isExternal' => false,
+        'params' => ['slug' => 'terms-and-conditions'],
+    ],
+];
+
+return[
+    'name' => 'ivnbg.com',
+    'logo' => 'ivnbg-logo',
+    'slogan' => 'Small guide to Nuremberg city',
+    'metaTitle' => 'Nuremberg City Guide - Explore the Best of Nuremberg with Local Insights',
+    'metaDescription' => 'Explore Nuremberg through the eyes of locals. Discover photo galleries, historical stories, hidden gems, and insider tips covering everything from the Imperial Castle to off-the-beaten-path courtyards.',
+    'metaKeywords' => 'Nuremberg, guide, city, travel, places, articles, photo galleries',
+    'gatMeasurementId' => env('MY_IVNBG_GTAG_MEASUREMENT_ID'),
+    'headerWidgets' => ['searchInContentType'=>['place','article'],'placeCategories'=>true],
+    'headerNav' => [
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+
+    ],
+    'drawerNav' => [
+        $myAppNav['placeIndex'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+    ],
+    'footerNav' => [
+        /*$myAppNav['articleIndex'],
+        $myAppNav['tagArticle'],*/
+        $myAppNav['placeIndex'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+        $myAppNav['termsAndConditions'],
+    ],
+];
\ No newline at end of file
diff --git a/config/projects/laravel-core.php b/config/projects/laravel-core.php
new file mode 100644
index 0000000..b670571
--- /dev/null
+++ b/config/projects/laravel-core.php
@@ -0,0 +1,92 @@
+ [
+        'name' => 'home',
+        'label' => 'home',
+        'uri' => 'home',
+        'isExternal' => false,
+    ],
+    'articleIndex' => [
+        'name' => 'articleIndex',
+        'label' => 'app.nav.articleIndex',
+        'uri' => 'blog',
+        'isExternal' => false,
+    ],
+    'tagArticle' => [
+        'name' => 'tagArticle',
+        'label' => 'app.nav.tags',
+        'uri' => 'blog/tags',
+        'isExternal' => false,
+    ],
+    'placeIndex' => [
+        'name' => 'placeIndex',
+        'label' => 'app.nav.placeIndex',
+        'uri' => 'places',
+        'isExternal' => false,
+
+    ],
+    'photoGalleryIndex' => [
+        'name' => 'photoGalleryIndex',
+        'label' => 'app.nav.photoGallery',
+        'uri' => 'photo-gallery',
+        'isExternal' => false,
+    ],
+    'contact' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.contact',
+        'hasIcon' => 'contact',
+        'uri' => 'contact',
+        'isExternal' => false,
+        'params' => ['slug' => 'contact'],
+    ],
+    'about' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.about',
+        'hasIcon' => 'info-circle',
+        'uri' => 'about',
+        'isExternal' => false,
+        'params' => ['slug' => 'about'],
+    ],
+    'termsAndConditions' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.termsAndConditions',
+        'hasIcon' => 'info-circle',
+        'uri' => 'terms-conditions',
+        'isExternal' => false,
+        'params' => ['slug' => 'terms-and-conditions'],
+    ],
+];
+
+return [
+    'name' => 'laravel-core.test',
+    'logo' => 'laravel-core-logo',
+    'slogan' => 'Laravel demo project',
+    'metaTitle' => 'Laravel Core Demo Project',
+    'metaDescription' => 'A Laravel demo project showcasing a content management system with articles, places, and photo galleries.',
+    'metaKeywords' => 'Laravel, demo, project, content management, articles, places, photo galleries',
+    'gatMeasurementId' => null,
+    'headerWidgets' => ['searchInContentType'=>['place','article'],'articleCategories'=>true, 'placeCategories'=>true],
+    'headerNav' => [
+        $myAppNav['tagArticle'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+    ],
+    'drawerNav' => [
+        $myAppNav['articleIndex'],
+        $myAppNav['tagArticle'],
+        $myAppNav['placeIndex'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+    ],
+    'footerNav' => [
+        $myAppNav['articleIndex'],
+        $myAppNav['tagArticle'],
+        $myAppNav['placeIndex'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+        $myAppNav['termsAndConditions'],
+    ],
+];
\ No newline at end of file
diff --git a/config/projects/martinvach.php b/config/projects/martinvach.php
new file mode 100644
index 0000000..3ad7696
--- /dev/null
+++ b/config/projects/martinvach.php
@@ -0,0 +1,89 @@
+ [
+        'name' => 'home',
+        'label' => 'app.nav.home',
+        'uri' => 'home',
+        'isExternal' => false,
+    ],
+    'articleIndex' => [
+        'name' => 'articleIndex',
+        'label' => 'app.nav.articleIndex',
+        'uri' => 'blog',
+        'isExternal' => false,
+    ],
+    'tagArticle' => [
+        'name' => 'tagArticle',
+        'label' => 'app.nav.tags',
+        'uri' => 'blog/tags',
+        'isExternal' => false,
+    ],
+    'placeIndex' => [
+        'name' => 'placeIndex',
+        'label' => 'app.nav.placeIndex',
+        'uri' => 'places',
+        'isExternal' => false,
+
+    ],
+    'photoGalleryIndex' => [
+        'name' => 'photoGalleryIndex',
+        'label' => 'app.nav.photoGallery',
+        'uri' => 'photo-gallery',
+        'isExternal' => false,
+    ],
+    'contact' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.contact',
+        'hasIcon' => 'contact',
+        'uri' => 'contact',
+        'isExternal' => false,
+        'params' => ['slug' => 'contact'],
+    ],
+    'about' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.about',
+        'hasIcon' => 'info-circle',
+        'uri' => 'about',
+        'isExternal' => false,
+        'params' => ['slug' => 'about'],
+    ],
+    'termsAndConditions' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.termsAndConditions',
+        'hasIcon' => 'info-circle',
+        'uri' => 'terms-conditions',
+        'isExternal' => false,
+        'params' => ['slug' => 'terms-and-conditions'],
+    ],
+];
+
+return[
+    'name' => 'martinvach.com',
+    'logo' => 'martinvach-logo',
+    'slogan' => 'See the World Differently',
+    'metaTitle' => 'Martin Vach Photography | Finding Beauty in Everyday Moments',
+    'metaDescription' => 'Transform the way you see the world. Martin Vach's photography reveals hidden beauty in everyday life, creating inspiring visual stories through unique perspective and masterful lighting.',
+    'metaKeywords' => 'landscape, nature, travel, fine art, photography',
+    'gatMeasurementId' => env('MY_MARTINVACH_GTAG_MEASUREMENT_ID'),
+    'headerWidgets' => ['searchInContentType'=>false,'articleCategories'=>false],
+    'headerNav' => [
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+        $myAppNav['termsAndConditions'],
+
+    ],
+    'drawerNav' => [
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+        $myAppNav['termsAndConditions'],
+    ],
+    'footerNav' => [
+        $myAppNav['home'],
+        $myAppNav['photoGalleryIndex'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+        $myAppNav['termsAndConditions'],
+    ],
+];
\ No newline at end of file
diff --git a/config/projects/vades.php b/config/projects/vades.php
new file mode 100644
index 0000000..39bbf98
--- /dev/null
+++ b/config/projects/vades.php
@@ -0,0 +1,70 @@
+ [
+        'name' => 'home',
+        'label' => 'app.nav.home',
+        'uri' => 'home',
+        'isExternal' => false,
+    ],
+    'articleIndex' => [
+        'name' => 'articleIndex',
+        'label' => 'app.nav.articleIndex',
+        'uri' => 'blog',
+        'isExternal' => false,
+    ],
+    'tagArticle' => [
+        'name' => 'tagArticle',
+        'label' => 'app.nav.tags',
+        'uri' => 'blog/tags',
+        'isExternal' => false,
+    ],
+    'contact' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.contact',
+        'hasIcon' => 'contact',
+        'uri' => 'contact',
+        'isExternal' => false,
+        'params' => ['slug' => 'contact'],
+    ],
+    'about' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.about',
+        'hasIcon' => 'info-circle',
+        'uri' => 'about',
+        'isExternal' => false,
+        'params' => ['slug' => 'about'],
+    ],
+    'termsAndConditions' => [
+        'name' => 'pageItem',
+        'label' => 'app.nav.termsAndConditions',
+        'hasIcon' => 'info-circle',
+        'uri' => 'terms-conditions',
+        'isExternal' => false,
+        'params' => ['slug' => 'terms-and-conditions'],
+    ],
+];
+
+return[
+    'name' => 'vades.dev',
+    'logo' => 'vades-logo-text',
+    'slogan' => 'The Whole Stack. One Engineer.',
+    'metaTitle' => 'Martin Vach — From IoT to AI: 20 Years of Shipping Software',
+    'metaDescription' => 'Most engineers own one layer. Martin Vach has spent 20 years working across all of them — infrastructure, frontend, and AI — on projects that could not afford to fail.',
+    'metaKeywords' => 'php, typescript, angular, laravel, symfony, mysql, postgresql, ai',
+    'gatMeasurementId' => env('MY_VADES_GTAG_MEASUREMENT_ID'),
+    'headerWidgets' => ['searchInContentType'=>false,'articleCategories'=>false],
+    'headerNav' => [
+        $myAppNav['about'],
+        $myAppNav['contact'],
+
+    ],
+    'drawerNav' => [
+        $myAppNav['about'],
+        $myAppNav['contact'],
+    ],
+    'footerNav' => [
+        $myAppNav['home'],
+        $myAppNav['about'],
+        $myAppNav['contact'],
+    ],
+];
diff --git a/config/query-builder.php b/config/query-builder.php
new file mode 100644
index 0000000..143fb82
--- /dev/null
+++ b/config/query-builder.php
@@ -0,0 +1,87 @@
+ [
+        'include' => 'include',
+        'filter' => 'filter',
+        'sort' => 'sort',
+        'fields' => 'fields',
+        'append' => 'append',
+    ],
+
+    /*
+     * The delimiter used to split array values in query parameters.
+     * For example: ?filter[name]=John,Jane uses ',' as delimiter.
+     */
+    'delimiter' => ',',
+
+    /*
+     * Related model aggregates are included using the relationship name suffixed with these strings.
+     * For example: GET /users?include=postsCount or GET /users?include=postsViewsSum
+     */
+    'suffixes' => [
+        'count' => 'Count',
+        'exists' => 'Exists',
+        'min' => 'Min',
+        'max' => 'Max',
+        'sum' => 'Sum',
+        'avg' => 'Avg',
+    ],
+
+    /*
+     * By default the package will throw an `InvalidFilterQuery` exception when a filter in the
+     * URL is not allowed in the `allowedFilters()` method.
+     */
+    'disable_invalid_filter_query_exception' => env('QUERY_BUILDER_DISABLE_INVALID_FILTER_QUERY_EXCEPTION', true),
+
+    /*
+     * By default the package will throw an `InvalidSortQuery` exception when a sort in the
+     * URL is not allowed in the `allowedSorts()` method.
+     */
+    'disable_invalid_sort_query_exception' => env('QUERY_BUILDER_DISABLE_INVALID_SORT_QUERY_EXCEPTION', true),
+
+    /*
+     * By default the package will throw an `InvalidIncludeQuery` exception when an include in the
+     * URL is not allowed in the `allowedIncludes()` method.
+     */
+    'disable_invalid_include_query_exception' => env('QUERY_BUILDER_DISABLE_INVALID_INCLUDE_QUERY_EXCEPTION', true),
+
+    /*
+     * By default, the package expects relationship names to be snake case plural when using fields[relationship].
+     * For example, fetching the id and name for a userOwner relation would look like this:
+     * GET /users?include=userOwner&fields[user_owners]=id,name
+     *
+     * Set this to `false` if you don't want that and keep the requested relationship names as-is and allows you to
+     * request the fields using a camelCase relationship name:
+     * GET /users?include=userOwner&fields[userOwner]=id,name
+     */
+    'convert_relation_names_to_snake_case_plural' => true,
+
+    /*
+     * This is an alternative to the previous option if you don't want to use default snake case plural for fields[relationship].
+     * It resolves the table name for the related model using the Laravel model class and, based on your chosen strategy,
+     * matches it with the fields[relationship] provided in the request.
+     *
+     * Set this to one of `snake_case`, `camelCase` or `none` if you want to enable table name resolution in addition to the relation name resolution.
+     * `snake_case` => Matches table names like 'topOrders' to `fields[top_orders]`
+     * `camelCase` => Matches table names like 'top_orders' to 'fields[topOrders]'
+     * `none` => Uses the exact table name
+     */
+    'convert_relation_table_name_strategy' => null,
+
+    /*
+     * By default, the package expects the field names to match the database names
+     * For example, fetching the field named firstName would look like this:
+     * GET /users?fields=firstName
+     *
+     * Set this to `true` if you want to convert the firstName into first_name for the underlying query
+     */
+    'convert_field_names_to_snake_case' => false,
+];
\ No newline at end of file
diff --git a/config/telescope.php b/config/telescope.php
index a241a34..daabc3b 100644
--- a/config/telescope.php
+++ b/config/telescope.php
@@ -113,7 +113,7 @@
     ],
 
     'ignore_paths' => [
-        'livewire*',
+      //  'livewire*',
         'nova-api*',
         'pulse*',
         '_boost*',
@@ -208,4 +208,4 @@
         Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
         Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
     ],
-];
+];
\ No newline at end of file
diff --git a/database/data/ivnbg-categories.csv b/database/data/ivnbg-categories.csv
new file mode 100644
index 0000000..2de141f
--- /dev/null
+++ b/database/data/ivnbg-categories.csv
@@ -0,0 +1,23 @@
+title,excerpt,slug
+Accomodation,Find perfect places to stay.,accomodation
+Attractions,Experience must-see attractions.,attractions
+Bridges,Walk across stunning and historic bridges.,bridges
+Buildings,Discover architectural landmarks.,buildings
+Castles,Explore historic castles.,castles
+Churches & Religious,Explore historic churches and religious sites.,churches
+Culture & Art,Explore cultural and artistic landmarks.,culture
+Districts,Explore Nuremberg districts.,districts
+Entertainment & Sport,Enjoy fun activities and sports events.,entertainment
+Events,Notable events hosted in Nuremberg.,events
+Food & Drink,Indulge in delicious food and drinks.,food
+Landmarks,Visit iconic landmarks and monuments.,landmarks
+Museums,Explore historical and cultural museums.,museums
+Nature & Outdoor,Discover nature and outdoor adventures.,nature
+Parks & Gardens,Relax in beautiful parks and gardens.,parks
+Places & Streets,Discover charming places and streets.,places
+Shopping & Markets,Enjoy shopping at local markets and stores.,shopping
+Statues & Fountains,Admire statues and iconic fountains.,statues
+Surroundings,Discover the scenic surroundings.,surroundings
+Towers,Visit historic and modern towers.,towers
+Transportation,Explore various transport hubs and systems.,transportation
+Waters & Rivers,Explore serene waters and rivers.,waters
\ No newline at end of file
diff --git a/database/data/vades-article-categories.csv b/database/data/vades-article-categories.csv
new file mode 100644
index 0000000..e620054
--- /dev/null
+++ b/database/data/vades-article-categories.csv
@@ -0,0 +1,2 @@
+title,excerpt,slug
+General,General categories,general
\ No newline at end of file
diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php
new file mode 100644
index 0000000..6685fa8
--- /dev/null
+++ b/database/factories/CategoryFactory.php
@@ -0,0 +1,87 @@
+
+ */
+class CategoryFactory extends Factory
+{
+    /**
+     * The name of the factory's corresponding model.
+     *
+     * @var string
+     */
+    protected $model = Category::class;
+
+    /**
+     * Define the model's default state.
+     *
+     * @return array
+     */
+    public function definition(): array
+    {
+        $projectId = Project::where('slug', AppProject::LaravelCore->value)->first()->id;
+        return [
+            'uuid' => $this->faker->unique()->uuid(),
+
+            // Constraint Note: Input requested specific ID '6' for mockup.
+            // Standard practice: Project::factory()
+            'project_id' => $projectId,
+
+            // Self-referencing relation. Default to null to avoid infinite recursion loops.
+            // You can use ->state() methods to create children specifically.
+            'parent_id' => null,
+
+            'status' => $this->faker->randomElement(array_column(ContentStatus::cases(), 'value')),
+
+            'visibility' => $this->faker->randomElement(array_column(ContentVisibility::cases(), 'value')),
+
+            // Constraint Note: Input requested 'post' for mockup.
+            'content_type' => $this->faker->randomElement(array_column(ContentContentType::cases(), 'value')),
+
+            'position' => $this->faker->numberBetween(0, 100),
+
+            // Unique constraint on [project_id, slug, lang].
+            // Since project_id is static (6), slug must be unique.
+            'slug' => $this->faker->unique()->slug(),
+
+            'lang' => $this->faker->randomElement(array_column(Language::cases(), 'value')),
+            'title' => $this->faker->sentence(4), // Generates a realistic title
+
+            // Nullable logic: 80% chance of text, 20% chance of null
+            'excerpt' => $this->faker->boolean(80) ? $this->faker->paragraph(2) : null,
+
+            // Nullable logic: 50% chance of metadata
+            'metadata' => $this->faker->boolean(50) ? [
+                'keywords' => $this->faker->words(3),
+                'author' => $this->faker->name(),
+                'color' => $this->faker->hexColor()
+            ] : null,
+
+            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+            'updated_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+            'deleted_at' => null, // Soft deletes are null by default
+        ];
+    }
+
+    /**
+     * Indicate that the category is a child of another category.
+     */
+    public function child(): static
+    {
+        return $this->state(fn (array $attributes) => [
+            'parent_id' => Category::factory(),
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/database/factories/ContentFactory.php b/database/factories/ContentFactory.php
new file mode 100644
index 0000000..457485d
--- /dev/null
+++ b/database/factories/ContentFactory.php
@@ -0,0 +1,51 @@
+faker->unique()->sentence(6, true);
+        $projectId = Project::where('slug', AppProject::LaravelCore->value)->first()->id;
+        return [
+            'uuid' => $this->faker->uuid(),
+            // Use lazy() or closures so these only execute if no value is provided
+            'project_id' => $projectId,
+            'user_id' => 1,
+            'author_id' => $this->faker->boolean(80) ? 1 : null,
+
+            'parent_id' => $this->faker->boolean(20) ? 1: null,
+
+            'content_type' => $this->faker->randomElement(array_column(ContentContentType::cases(), 'value')),
+            'status' => $this->faker->randomElement(array_column(ContentStatus::cases(), 'value')),
+            'visibility' => $this->faker->randomElement(array_column(ContentVisibility::cases(), 'value')),
+            'lang' => $this->faker->randomElement(array_column(Language::cases(), 'value')),
+
+            'title' => $title,
+            'slug' => Str::slug($title),
+            'subtitle' => $this->faker->boolean(60) ? $this->faker->sentence(8, true) : null,
+            'excerpt' => $this->faker->boolean(80) ? $this->faker->paragraph() : null,
+            'content' => $this->faker->boolean(90) ? $this->faker->paragraphs($this->faker->numberBetween(3, 8), true) : null,
+            'metadata' => $this->faker->boolean(30) ? json_encode(['ref' => $this->faker->uuid, 'featured_image' => $this->faker->imageUrl()]) : null,
+            'position' => $this->faker->numberBetween(0, 20),
+            'is_featured' => $this->faker->boolean(10),
+            'published_at' => $this->faker->boolean(70) ? $this->faker->dateTimeBetween('-2 years', 'now') : null,
+        ];
+    }
+}
\ No newline at end of file
diff --git a/database/factories/InquiryFactory.php b/database/factories/InquiryFactory.php
new file mode 100644
index 0000000..62a0221
--- /dev/null
+++ b/database/factories/InquiryFactory.php
@@ -0,0 +1,41 @@
+
+ */
+class InquiryFactory extends Factory
+{
+    protected $model = Inquiry::class;
+
+    /**
+     * @return array
+     */
+    public function definition(): array
+    {
+        $projectId = Project::where('slug', AppProject::LaravelCore->value)->first()->id;
+        return [
+            'project_id' =>$projectId,
+            'user_id' => $this->faker->boolean(80) ? 1 : null,
+            'is_read' => $this->faker->boolean(),
+            'is_spam' => $this->faker->boolean(10),
+            'is_archived' => $this->faker->boolean(10),
+            'name' => $this->faker->name(),
+            'email' => $this->faker->unique()->safeEmail(),
+            'subject' => $this->faker->boolean(85) ? $this->faker->sentence(6, true) : null,
+            'message' => $this->faker->paragraphs($this->faker->numberBetween(1, 3), true),
+            'ip_address' => $this->faker->boolean(90) ? $this->faker->ipv4() : null,
+            'user_agent' => $this->faker->boolean(90) ? $this->faker->userAgent() : null,
+            'terms_accepted_at' => $this->faker->boolean(95) ? $this->faker->dateTimeBetween('-2 years', 'now') : null,
+            'metadata' => $this->faker->boolean(20) ? json_encode(['referrer' => $this->faker->url()]) : null,
+            // ...timestamps and softDeletes handled by Eloquent...
+        ];
+    }
+}
\ No newline at end of file
diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php
new file mode 100644
index 0000000..21657b6
--- /dev/null
+++ b/database/factories/ProjectFactory.php
@@ -0,0 +1,54 @@
+
+ */
+class ProjectFactory extends Factory
+{
+    /**
+     * The name of the factory's corresponding model.
+     *
+     * @var string
+     */
+    protected $model = Project::class;
+
+    /**
+     * Define the model's default state.
+     *
+     * @return array
+     */
+    public function definition(): array
+    {
+        return [
+            // Unique slug (e.g., "project-alpha" or "cool-startup")
+            'slug' => $this->faker->unique()->slug(2),
+
+            // Nullable excerpt: 80% chance of text, 20% chance of null
+            'excerpt' => $this->faker->boolean(80)
+                ? $this->faker->paragraph(2)
+                : null,
+
+            // Nullable JSON metadata: 70% chance of data
+            'metadata' => $this->faker->boolean(70) ? [
+                'website' => $this->faker->url(),
+                'client' => $this->faker->company(),
+                'version' => $this->faker->semver(),
+                'tags' => $this->faker->words(3),
+            ] : null,
+
+            // Standard timestamps are handled automatically,
+            // but we can randomize creation time if desired
+            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+            'updated_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+
+            // Soft deletes default to null (active record)
+            'deleted_at' => null,
+        ];
+    }
+}
diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php
new file mode 100644
index 0000000..eb5a3a9
--- /dev/null
+++ b/database/factories/TagFactory.php
@@ -0,0 +1,34 @@
+
+ */
+class TagFactory extends Factory
+{
+    protected $model = Tag::class;
+
+    /**
+     * @return array
+     */
+    public function definition(): array
+    {
+        $projectId = Project::where('slug', AppProject::LaravelCore->value)->first()->id;
+        return [
+            'project_id' => $projectId,
+            'content_type' => $this->faker->randomElement(array_column(ContentContentType::cases(), 'value')),
+            'lang' => $this->faker->randomElement(array_column(Language::cases(), 'value')),
+            'name' => $this->faker->unique()->words(2, true),
+            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+            'updated_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+        ];
+    }
+}
\ No newline at end of file
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
index 07af023..7ce8fc1 100644
--- a/database/factories/UserFactory.php
+++ b/database/factories/UserFactory.php
@@ -1,7 +1,11 @@
 value)->first()->id;
         return [
+            'uuid' => $this->faker->unique()->uuid(),
             'name' => fake()->name(),
             'email' => fake()->unique()->safeEmail(),
             'email_verified_at' => now(),
             'password' => static::$password ??= Hash::make('password'),
             'remember_token' => Str::random(10),
-            'two_factor_secret' => Str::random(10),
-            'two_factor_recovery_codes' => Str::random(10),
-            'two_factor_confirmed_at' => now(),
+
+            // --- New Schema Fields ---
+
+            // Automatically create a project if one isn't passed in
+            'project_id' =>$projectId,
+
+            // Match your 'role' default ('user') but allow randomness
+            'role' => fake()->randomElement(['user', 'admin', 'editor']),
+
+            // Match your 'account_type' default ('free') but allow randomness
+            'account_type' => fake()->randomElement(['free', 'pro', 'enterprise']),
+
+            // Nullable JSON metadata
+            'metadata' => fake()->boolean(70) ? [
+                'theme' => fake()->randomElement(['light', 'dark']),
+                'notifications' => fake()->boolean(),
+                'last_login_ip' => fake()->ipv4(),
+            ] : null,
+
+            // Standard timestamps
+            'created_at' => now(),
+            'updated_at' => now(),
         ];
     }
 
@@ -44,16 +76,4 @@ public function unverified(): static
             'email_verified_at' => null,
         ]);
     }
-
-    /**
-     * Indicate that the model does not have two-factor authentication configured.
-     */
-    public function withoutTwoFactor(): static
-    {
-        return $this->state(fn (array $attributes) => [
-            'two_factor_secret' => null,
-            'two_factor_recovery_codes' => null,
-            'two_factor_confirmed_at' => null,
-        ]);
-    }
-}
+}
\ No newline at end of file
diff --git a/database/migrations/2026_01_05_184533_create_projects_table.php b/database/migrations/2026_01_05_184533_create_projects_table.php
index bb04b66..a7972eb 100644
--- a/database/migrations/2026_01_05_184533_create_projects_table.php
+++ b/database/migrations/2026_01_05_184533_create_projects_table.php
@@ -14,6 +14,7 @@ public function up(): void
         Schema::create('projects', function (Blueprint $table) {
             $table->id();
             $table->string('slug')->unique();
+            $table->string('url')->unique();
             $table->text('excerpt')->nullable();
             $table->json('metadata')->nullable();
             $table->timestamps();
@@ -28,4 +29,4 @@ public function down(): void
     {
         Schema::dropIfExists('projects');
     }
-};
+};
\ No newline at end of file
diff --git a/database/migrations/2026_01_05_184613_add_project_id_to_users_table.php b/database/migrations/2026_01_05_184613_add_project_id_to_users_table.php
index b4815a3..666080d 100644
--- a/database/migrations/2026_01_05_184613_add_project_id_to_users_table.php
+++ b/database/migrations/2026_01_05_184613_add_project_id_to_users_table.php
@@ -9,6 +9,7 @@
     public function up(): void
     {
         Schema::table('users', function (Blueprint $table) {
+            $table->uuid('uuid')->after('id')->unique();
             // We use 'after' to position columns logically, though not strictly required by SQL
             $table->foreignId('project_id')
                 //->nullable() // Nullable initially to handle existing users, remove if fresh DB
diff --git a/database/migrations/2026_01_05_185832_create_tags_table.php b/database/migrations/2026_01_05_185832_create_tags_table.php
index b4db1c3..a80ef0a 100644
--- a/database/migrations/2026_01_05_185832_create_tags_table.php
+++ b/database/migrations/2026_01_05_185832_create_tags_table.php
@@ -14,8 +14,7 @@ public function up(): void
 
             $table->string('content_type', 20);
             $table->string('lang', 10)->default('en');
-            $table->unsignedBigInteger('views_count')->default(0);
-            $table->string('name', 255);
+             $table->string('name', 255);
 
             $table->timestamps();
             $table->softDeletes();
@@ -28,4 +27,4 @@ public function down(): void
     {
         Schema::dropIfExists('tags');
     }
-};
+};
\ No newline at end of file
diff --git a/database/migrations/2026_01_05_122955_create_telescope_entries_table.php b/database/migrations/2026_03_20_131444_create_telescope_entries_table.php
similarity index 98%
rename from database/migrations/2026_01_05_122955_create_telescope_entries_table.php
rename to database/migrations/2026_03_20_131444_create_telescope_entries_table.php
index 700a83f..031b6f4 100644
--- a/database/migrations/2026_01_05_122955_create_telescope_entries_table.php
+++ b/database/migrations/2026_03_20_131444_create_telescope_entries_table.php
@@ -48,7 +48,7 @@ public function up(): void
             $table->foreign('entry_uuid')
                 ->references('uuid')
                 ->on('telescope_entries')
-                ->onDelete('cascade');
+                ->cascadeOnDelete();
         });
 
         $schema->create('telescope_monitoring', function (Blueprint $table) {
diff --git a/database/seeders/CategoryContentSeeder.php b/database/seeders/CategoryContentSeeder.php
new file mode 100644
index 0000000..7dc6803
--- /dev/null
+++ b/database/seeders/CategoryContentSeeder.php
@@ -0,0 +1,33 @@
+get();
+        $contents = Content::withoutGlobalScopes()->get();
+
+        // 2. Quick check to see if we actually have data to work with
+        if ($categories->isEmpty() || $contents->isEmpty()) {
+            $this->command->warn("Seeding skipped: Ensure 'categories' and 'contents' tables have data first.");
+            return;
+        }
+
+        // 3. Attach relationships
+        $categories->each(function ($category) use ($contents) {
+            // Pick a random number of items to attach (e.g., between 1 and 3)
+            $randomContents = $contents->random(min(rand(1, 3), $contents->count()));
+
+            $category->contents()->attach($randomContents->pluck('id'));
+        });
+
+        $this->command->info("Successfully linked categories and content.");
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php
new file mode 100644
index 0000000..b049546
--- /dev/null
+++ b/database/seeders/CategorySeeder.php
@@ -0,0 +1,33 @@
+value)->first()->id;
+
+        Category::factory()->count(10)->create([
+                                                  'project_id' => $projectId,
+                                                   'content_type' => ContentContentType::Article->value,
+                                                   'status' =>ContentStatus::Published->value,
+                                                   'visibility' =>ContentVisibility::Public->value,
+                                                   'lang' => Language::EN->value,
+                                              ]);
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/CategorySeederProject.php b/database/seeders/CategorySeederProject.php
new file mode 100644
index 0000000..f28cc1d
--- /dev/null
+++ b/database/seeders/CategorySeederProject.php
@@ -0,0 +1,49 @@
+first()->id;
+        $this->storeData('data/vades-article-categories.csv',  $vadesProjectId,ContentContentType::Article->value);*/
+
+        $ivnbgProjectId = Project::where('slug',AppProject::Ivnbg)->first()->id;
+        $this->storeData('data/ivnbg-categories.csv', $ivnbgProjectId,ContentContentType::Place->value);
+    }
+
+    private function storeData(string $filePath, int $projectId, string $contentType): void
+    {
+        $csvPath = database_path($filePath);
+        $rows = array_map('str_getcsv', file($csvPath));
+        $header = array_map('trim', array_shift($rows));
+
+        foreach ($rows as $row) {
+            $data = array_combine($header, $row);
+
+            // Set required fields not present in CSV
+            $data['uuid']          = (string) Str::uuid();
+            $data['project_id']    = $projectId;
+            $data['content_type']  = $contentType;
+            $data['status']        = 'published';
+            $data['visibility']    = 'public';
+            $data['lang']          = 'en';
+
+
+            Category::create($data);
+        }
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/ContentSeeder.php b/database/seeders/ContentSeeder.php
new file mode 100644
index 0000000..3794257
--- /dev/null
+++ b/database/seeders/ContentSeeder.php
@@ -0,0 +1,28 @@
+where('slug', AppProject::LaravelCore->value)->value('id');
+
+        Content::factory()->count(50)->create([
+                                                  'project_id' => $projectId,
+                                                  'content_type' => ContentContentType::Article->value,
+                                                  'status' =>ContentStatus::Published->value,
+                                                  'visibility' =>ContentVisibility::Public->value,
+                                                  'lang' => Language::EN->value,
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/ContentTagSeeder.php b/database/seeders/ContentTagSeeder.php
new file mode 100644
index 0000000..89e6015
--- /dev/null
+++ b/database/seeders/ContentTagSeeder.php
@@ -0,0 +1,31 @@
+get();
+        $tags = Tag::withoutGlobalScopes()->get();
+
+        if ($contents->isEmpty() || $tags->isEmpty()) {
+            $this->command->warn("Seeding skipped: Ensure 'contents' and 'tags' tables have data.");
+            return;
+        }
+
+        // Loop through contents and attach 1-5 random tags to each
+        $contents->each(function ($content) use ($tags) {
+            $randomTags = $tags->random(min(rand(1, 5), $tags->count()));
+
+            $content->tags()->attach($randomTags->pluck('id'));
+        });
+
+        $this->command->info("Successfully attached tags to content.");
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 1fb527e..24cd6a4 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -2,10 +2,7 @@
 
 namespace Database\Seeders;
 
-use App\Models\Project;
-use App\Models\User;
 use Illuminate\Database\Seeder;
-use Illuminate\Support\Facades\Hash;
 
 class DatabaseSeeder extends Seeder
 {
@@ -14,60 +11,24 @@ class DatabaseSeeder extends Seeder
      */
     public function run(): void
     {
-        // 1. Define the projects data
-        $projects = [
-            [
-               'slug' => 'ivnbg',
-                'excerpt' => 'ivnbg.com project',
-                'metadata' => ['url' => 'www.ivnbg.com'],
-            ],
-            [
-               'slug' => 'martinvach',
-                'excerpt' => 'martinvach.com project',
-                'metadata' => ['url' => 'www.martinvach.com'],
-            ],
-            [
-               'slug' => 'myprompties',
-                'excerpt' => 'myprompties.com project',
-                'metadata' => ['url' => 'www.myprompties.com'],
-            ],
-            [
-               'slug' => 'vades',
-                'excerpt' => 'vades.dev project',
-                'metadata' => ['url' => 'www.vades.dev'],
-            ],
-            [
-               'slug' => 'aitomatix',
-                'excerpt' => 'aitomatix.com project',
-                'metadata' => ['url' => 'www.aitomatix.com'],
-            ],
-            [
-               'slug' => 'laravel-core',
-                'excerpt' => 'laravel-core.test project. Only for local testing purposes.',
-                'metadata' => null,
-            ],
-        ];
 
-        // 2. Loop and use firstOrCreate for each project
-        foreach ($projects as $projectData) {
-            Project::firstOrCreate(
-                ['slug' => $projectData['slug']], // Search key (Unique)
-                [
-                    'excerpt' => $projectData['excerpt'],
-                    'metadata' => $projectData['metadata'] ?? null,
-                ]
-            );
-        }
+        $this->call([
+                        ProjectSeeder::class,
+                        UserSeeder::class,
+                        CategorySeederProject::class,
+                    ]);
+
+        if (app()->environment('local')) {
+            $this->call([
+                            CategorySeeder::class,
+                            TagSeeder::class,
+                            InquirySeeder::class,
+                            ContentSeeder::class,
+                            CategoryContentSeeder::class,
+                            ContentTagSeeder::class,
 
-        // 3. Create the Test User
-        User::firstOrCreate(
-            ['email' => 'test@example.com'], // Search key (Unique)
-            [
-                'name' => 'Test User',
-                'project_id' => Project::where('slug', 'laravel-core.test')->first()->id,
-                'password' => Hash::make('password'),
-                'email_verified_at' => now(),
-            ]
-        );
+                        ]);
+
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/database/seeders/InquirySeeder.php b/database/seeders/InquirySeeder.php
new file mode 100644
index 0000000..dd02adc
--- /dev/null
+++ b/database/seeders/InquirySeeder.php
@@ -0,0 +1,20 @@
+value)->first()->id;
+
+        Inquiry::factory()->count(50)->create([
+            'project_id' => $projectId,
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/ProjectSeeder.php b/database/seeders/ProjectSeeder.php
new file mode 100644
index 0000000..f13d16b
--- /dev/null
+++ b/database/seeders/ProjectSeeder.php
@@ -0,0 +1,70 @@
+ AppProject::LaravelCore->value,
+                'excerpt' => 'laravel-core.test project. Only for local testing purposes.',
+                'url' => AppProject::LaravelCore->getUrl(),
+                'metadata' => [ ],
+            ],
+            [
+                'slug' =>AppProject::Ivnbg->value,
+                'excerpt' => 'ivnbg.com project',
+              'url' => AppProject::Ivnbg->getUrl(),
+                'metadata' => [ ],
+            ],
+            [
+                'slug' => AppProject::MartinVach->value,
+                'excerpt' => 'martinvach.com project',
+                'url' => AppProject::MartinVach->getUrl(),
+                'metadata' => [ ],
+            ],
+            [
+                'slug' => AppProject::MyPrompties->value,
+                'excerpt' => 'myprompties.com project',
+                'url' => AppProject::MyPrompties->getUrl(),
+                'metadata' => [ ],
+            ],
+            [
+                'slug' => AppProject::Vades->value,
+                'excerpt' => 'vades.dev project',
+                'url' => AppProject::Vades->getUrl(),
+                'metadata' => [ ],
+            ],
+            [
+                'slug' => AppProject::Aitomatix->value,
+                'excerpt' => 'aitomatix.com project',
+                'url' => AppProject::Aitomatix->getUrl(),
+                'metadata' => [ ],
+            ],
+        ];
+
+        foreach ($projects as &$project) {
+            if (is_array($project['metadata']) || is_object($project['metadata'])) {
+                $project['metadata'] = json_encode($project['metadata']);
+            }
+        }
+        unset($project);
+
+        Project::upsert(
+            $projects,
+            ['slug'],
+            ['excerpt', 'metadata']
+        );
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/TagSeeder.php b/database/seeders/TagSeeder.php
new file mode 100644
index 0000000..0933eb1
--- /dev/null
+++ b/database/seeders/TagSeeder.php
@@ -0,0 +1,23 @@
+value)->first()->id;
+        Tag::factory()->count(50)->create([
+            'project_id' =>  $projectId,
+                                              'content_type' => ContentContentType::Article->value,
+                                              'lang' => Language::EN->value,
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php
new file mode 100644
index 0000000..793503f
--- /dev/null
+++ b/database/seeders/UserSeeder.php
@@ -0,0 +1,40 @@
+ 'test@example.com'], // Search Key
+            [
+                'uuid' => Str::uuid()->toString(),
+                'name' => 'Test User',
+                'password' => Hash::make('password'),
+                'email_verified_at' => now(),
+
+                // Specific fields for your login testing
+                // In database/factories/UserFactory.php
+                'project_id' => Project::where('slug', AppProject::LaravelCore->value)->first()->id,
+                'role' =>UserRole::User->value,     // Override random factory default
+                'account_type' => 'pro', // Override random factory default
+                'metadata' => ['is_super_admin' => true],
+            ]
+        );
+    }
+}
\ No newline at end of file
diff --git a/docs/CacheService.md b/docs/CacheService.md
new file mode 100644
index 0000000..f2c93a7
--- /dev/null
+++ b/docs/CacheService.md
@@ -0,0 +1,353 @@
+# CacheService
+
+`App\Services\CacheService`
+
+A Laravel service for generating consistent, domain-scoped cache keys and interacting with the cache store. Keys are automatically namespaced to the current HTTP request host, ensuring entries never collide across multi-tenant or multi-domain applications.
+
+---
+
+## Installation
+
+Place `CacheService.php` in `app/Services/Cache/` and `cacheDuration` in `config/myapp`.
+
+Register the service as a singleton in `AppServiceProvider` (optional, but recommended to avoid repeated instantiation):
+
+```php
+// app/Providers/AppServiceProvider.php
+public function register(): void
+{
+    $this->app->singleton(\App\Services\CacheService::class);
+}
+```
+
+---
+
+## Configuration
+
+**`config/myapp.php`**
+
+```php
+return [
+      'cacheDuration' => (int) env('MY_CACHE_DURATION', 86400),
+];
+```
+
+Override the default TTL in your `.env` file:
+
+```env
+MY_CACHE_DURATION=3600  # 1 hour
+```
+
+If the config key is missing entirely, the service falls back to `86400` seconds (1 day).
+
+---
+
+## Cache Key Format
+
+Every key follows this structure:
+
+```
+{domain}:{cacheName}:{hash}
+```
+
+| Segment | Description |
+|---|---|
+| `domain` | Normalized hostname from the current HTTP request (e.g. `example_com`) |
+| `cacheName` | The name you provide, optionally suffixed with the normalized `contentType` |
+| `hash` | 8-character MD5 hash of `domain:cacheName` — ensures uniqueness and prevents collisions |
+
+**Examples:**
+
+| `$cacheName` | `$contentType` | Host | Generated key |
+|---|---|---|---|
+| `products` | `null` | `example.com` | `example_com:products:a1b2c3d4` |
+| `products` | `application/json` | `example.com` | `example_com:products_application_json:9f8e7d6c` |
+| `categories` | `news` | `api.example.com` | `api_example_com:categories_news:3c2b1a0f` |
+
+> **Note:** When running in CLI context (e.g. Artisan commands or queues), the domain segment falls back to `cli` since there is no active HTTP request.
+
+---
+
+## API Reference
+
+### `generateCacheName()`
+
+Generates a unique, deterministic cache key without storing anything.
+
+```php
+public function generateCacheName(string $cacheName, ?string $contentType = null): string
+```
+
+**Parameters**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `$cacheName` | `string` | Yes | Logical name for the cache entry |
+| `$contentType` | `string\|null` | No | Optional content type appended to the name (e.g. `application/json`, `text/html`) |
+
+**Returns** `string` — the full cache key.
+
+```php
+$key = $this->cache->generateCacheName('products');
+// → "example_com:products:a1b2c3d4"
+
+$key = $this->cache->generateCacheName('products', 'application/json');
+// → "example_com:products_application_json:9f8e7d6c"
+```
+
+---
+
+### `put()`
+
+Stores a value in the cache and returns the key used.
+
+```php
+public function put(
+    string $cacheName,
+    mixed $value,
+    ?string $contentType = null,
+    ?int $duration = null
+): string
+```
+
+**Parameters**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `$cacheName` | `string` | Yes | Logical name for the cache entry |
+| `$value` | `mixed` | Yes | The value to store |
+| `$contentType` | `string\|null` | No | Optional content type |
+| `$duration` | `int\|null` | No | TTL in seconds. Defaults to `config('cache_service.default_duration')` |
+
+**Returns** `string` — the cache key that was used.
+
+```php
+$key = $this->cache->put('products', $products);
+$key = $this->cache->put('products', $products, 'application/json');
+$key = $this->cache->put('products', $products, 'application/json', 3600);
+```
+
+---
+
+### `get()`
+
+Retrieves a cached value by name and optional content type.
+
+```php
+public function get(string $cacheName, ?string $contentType = null, mixed $default = null): mixed
+```
+
+**Parameters**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `$cacheName` | `string` | Yes | Logical name of the cache entry |
+| `$contentType` | `string\|null` | No | Must match what was used when storing |
+| `$default` | `mixed` | No | Returned on a cache miss. Defaults to `null` |
+
+**Returns** `mixed` — the cached value, or `$default` on miss.
+
+```php
+$products = $this->cache->get('products');
+$products = $this->cache->get('products', 'application/json');
+$products = $this->cache->get('products', null, []);
+```
+
+---
+
+### `getByName()`
+
+Retrieves a cached value directly by a previously generated key string. Useful when the key was stored externally or returned by `put()`.
+
+```php
+public function getByName(string $cacheName, mixed $default = null): mixed
+```
+
+**Parameters**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `$cacheName` | `string` | Yes | A full key string as returned by `generateCacheName()` or `put()` |
+| `$default` | `mixed` | No | Returned on a cache miss. Defaults to `null` |
+
+**Returns** `mixed` — the cached value, or `$default` on miss.
+
+```php
+$key      = $this->cache->put('products', $products);
+$products = $this->cache->getByName($key);
+```
+
+---
+
+### `has()`
+
+Checks whether a cache entry exists for the given name and content type.
+
+```php
+public function has(string $cacheName, ?string $contentType = null): bool
+```
+
+**Returns** `bool`
+
+```php
+if ($this->cache->has('products')) { ... }
+if ($this->cache->has('products', 'application/json')) { ... }
+```
+
+---
+
+### `forget()`
+
+Removes a specific cache entry.
+
+```php
+public function forget(string $cacheName, ?string $contentType = null): bool
+```
+
+**Returns** `bool` — `true` on success.
+
+```php
+$this->cache->forget('products');
+$this->cache->forget('products', 'application/json');
+```
+
+---
+
+### `remember()`
+
+Returns the cached value if it exists, otherwise executes the callback, stores the result, and returns it. This is the primary method for most use cases.
+
+```php
+public function remember(
+    string $cacheName,
+    \Closure $callback,
+    ?string $contentType = null,
+    ?int $duration = null
+): mixed
+```
+
+**Parameters**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `$cacheName` | `string` | Yes | Logical name for the cache entry |
+| `$callback` | `Closure` | Yes | Executed on cache miss; its return value is stored and returned |
+| `$contentType` | `string\|null` | No | Optional content type |
+| `$duration` | `int\|null` | No | TTL in seconds. Defaults to `config('cache_service.default_duration')` |
+
+**Returns** `mixed` — the cached or freshly computed value.
+
+```php
+$products = $this->cache->remember(
+    cacheName: 'products',
+    callback:  fn() => Product::published()->get(),
+);
+
+// With content type
+$products = $this->cache->remember(
+    cacheName:   'products',
+    callback:    fn() => Product::published()->get(),
+    contentType: 'application/json',
+);
+
+// With custom TTL
+$products = $this->cache->remember(
+    cacheName: 'products',
+    callback:  fn() => Product::published()->get(),
+    duration:  3600,
+);
+```
+
+---
+
+## Usage Examples
+
+### Dependency Injection
+
+```php
+use App\Services\CacheService;
+
+class ProductController extends Controller
+{
+    public function __construct(private CacheService $cache) {}
+
+    public function index()
+    {
+        $products = $this->cache->remember(
+            cacheName: 'products',
+            callback:  fn() => Product::published()->get(),
+        );
+
+        return view('products.index', compact('products'));
+    }
+}
+```
+
+### View Composer
+
+```php
+class CategoryComposer
+{
+    public function __construct(private CacheService $cache) {}
+
+    public function compose(View $view): void
+    {
+        $categoryType = $view->getData()['categoryType'] ?? '';
+
+        $categories = $this->cache->remember(
+            cacheName:   'categories',
+            callback:    fn() => Category::publishedByType($categoryType)
+                                         ->withCount('contents')
+                                         ->where('contents_count', '>', 0)
+                                         ->get(),
+            contentType: $categoryType ?: null,
+        );
+
+        $view->with([
+            'composerCategories'      => $categories,
+            'composerCurrentCategory' => request()->query('category'),
+        ]);
+    }
+}
+```
+
+### Manual Store and Retrieve
+
+```php
+// Store and capture the key
+$key = $this->cache->put('settings', $settings, 'application/json', 7200);
+
+// Retrieve later by key string
+$settings = $this->cache->getByName($key);
+```
+
+### Cache Invalidation
+
+```php
+// Forget a specific variant
+$this->cache->forget('products', 'application/json');
+
+// Forget without content type
+$this->cache->forget('products');
+```
+
+---
+
+## Private Methods
+
+These are internal to the service and not part of the public API.
+
+### `defaultDuration()`
+
+Reads `config('cache_service.default_duration')` and returns it as an `int`. Falls back to `86400` if the config key is absent.
+
+### `normalizeDomain()`
+
+Reads the hostname from `Request::getHost()`, lowercases it, and replaces all non-alphanumeric characters with underscores. Falls back to `'cli'` outside HTTP context.
+
+| Input | Output |
+|---|---|
+| `example.com` | `example_com` |
+| `api.example.com` | `api_example_com` |
+| `localhost:8000` | `localhost_8000` |
+| _(Artisan / queue)_ | `cli` |
\ No newline at end of file
diff --git a/lang/en/app.php b/lang/en/app.php
new file mode 100644
index 0000000..23589f4
--- /dev/null
+++ b/lang/en/app.php
@@ -0,0 +1,74 @@
+ [
+        'about' => 'About',
+        'contact' => 'Contact',
+        'home' => 'Home',
+        'categories' => 'Categories',
+        'tags' => 'Tags',
+        'search' => 'Search',
+        'placeCategoryList' => 'Categories',
+        'placeIndex' => 'Places',
+        'readMore' => 'Read more',
+        'readMoreAbout' => 'Read more about :about',
+        'articleIndex' => 'Blog',
+        'photoGallery' => 'Photo gallery',
+        'bestPlacesToVisitIn' => 'Best places to visit in :name',
+        'seeMore' => 'See more',
+        'otherPlaces' => 'Other places',
+        'all' => 'All',
+        'allArticles' => 'All articles',
+        'allPlaces' => 'All places',
+        'allImages' => 'All images',
+        'all_' => 'All :name',
+        'recentPosts' => 'Recent posts',
+        'listOfPlaces' => 'List of places',
+        'address' => 'Address',
+        'highlights' => 'Highlights',
+        'takeMeHome' => 'Take me home',
+        'previous' => 'Previous',
+        'next' => 'Next',
+        "termsAndConditions" => "Terms and Conditions",
+    ],
+    'label' => [
+        'placesInCategory' => 'Other places in category :category',
+        'relatedPlaces' => 'Related places',
+        'highlights' => 'highlights',
+    ],
+
+    'search' => [
+        'all' => 'Search the site',
+        'blog' => 'Search in blog',
+        'title' => 'Search results for ":query"',
+        'noResults' => 'No results found for ":query"',
+    ],
+
+    'error' => [
+        '404' => [
+            'title' => "Oops! This page isn't available",
+            'message' => "We couldn't locate the page you requested. It might have moved, been deleted, or perhaps there's a typo in the address. Please check the URL and try again.",
+        ],
+        '500' => [
+            'title' => 'Oops! Internal Server Error',
+            'message' => "We're experiencing an unexpected issue with our server. Please try again in a few minutes.",
+        ],
+    ],
+    'form' => [
+        'name' => 'Name',
+        'yourName' => 'Your name',
+        'email' => 'Email',
+        'yourEmail' => 'Your email address',
+        'message' => 'Message',
+        'leaveMessage' => 'Leave us a message',
+        'submit' => 'Submit',
+        'successContactForm' => 'Your message has been sent successfully!',
+        'errorContactForm' => 'An error occurred while sending your message. Please try again.',
+        'contactUs' => 'Contact Us',
+
+        'followUs' => 'Follow us on social media',
+        'phone' => 'Phone',
+        'emailAddress' => 'Email Address',
+        'location' => 'Location',
+    ],
+];
diff --git a/lang/en/auth.php b/lang/en/auth.php
new file mode 100644
index 0000000..6598e2c
--- /dev/null
+++ b/lang/en/auth.php
@@ -0,0 +1,20 @@
+ 'These credentials do not match our records.',
+    'password' => 'The provided password is incorrect.',
+    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+
+];
diff --git a/lang/en/pagination.php b/lang/en/pagination.php
new file mode 100644
index 0000000..d481411
--- /dev/null
+++ b/lang/en/pagination.php
@@ -0,0 +1,19 @@
+ '« Previous',
+    'next' => 'Next »',
+
+];
diff --git a/lang/en/passwords.php b/lang/en/passwords.php
new file mode 100644
index 0000000..fad3a7d
--- /dev/null
+++ b/lang/en/passwords.php
@@ -0,0 +1,22 @@
+ 'Your password has been reset.',
+    'sent' => 'We have emailed your password reset link.',
+    'throttled' => 'Please wait before retrying.',
+    'token' => 'This password reset token is invalid.',
+    'user' => "We can't find a user with that email address.",
+
+];
diff --git a/lang/en/validation.php b/lang/en/validation.php
new file mode 100644
index 0000000..dddc947
--- /dev/null
+++ b/lang/en/validation.php
@@ -0,0 +1,194 @@
+ 'The :attribute field must be accepted.',
+    'accepted_if' => 'The :attribute field must be accepted when :other is :value.',
+    'active_url' => 'The :attribute field must be a valid URL.',
+    'after' => 'The :attribute field must be a date after :date.',
+    'after_or_equal' => 'The :attribute field must be a date after or equal to :date.',
+    'alpha' => 'The :attribute field must only contain letters.',
+    'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.',
+    'alpha_num' => 'The :attribute field must only contain letters and numbers.',
+    'array' => 'The :attribute field must be an array.',
+    'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.',
+    'before' => 'The :attribute field must be a date before :date.',
+    'before_or_equal' => 'The :attribute field must be a date before or equal to :date.',
+    'between' => [
+        'array' => 'The :attribute field must have between :min and :max items.',
+        'file' => 'The :attribute field must be between :min and :max kilobytes.',
+        'numeric' => 'The :attribute field must be between :min and :max.',
+        'string' => 'The :attribute field must be between :min and :max characters.',
+    ],
+    'boolean' => 'The :attribute field must be true or false.',
+    'can' => 'The :attribute field contains an unauthorized value.',
+    'confirmed' => 'The :attribute field confirmation does not match.',
+    'contains' => 'The :attribute field is missing a required value.',
+    'current_password' => 'The password is incorrect.',
+    'date' => 'The :attribute field must be a valid date.',
+    'date_equals' => 'The :attribute field must be a date equal to :date.',
+    'date_format' => 'The :attribute field must match the format :format.',
+    'decimal' => 'The :attribute field must have :decimal decimal places.',
+    'declined' => 'The :attribute field must be declined.',
+    'declined_if' => 'The :attribute field must be declined when :other is :value.',
+    'different' => 'The :attribute field and :other must be different.',
+    'digits' => 'The :attribute field must be :digits digits.',
+    'digits_between' => 'The :attribute field must be between :min and :max digits.',
+    'dimensions' => 'The :attribute field has invalid image dimensions.',
+    'distinct' => 'The :attribute field has a duplicate value.',
+    'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.',
+    'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.',
+    'email' => 'The :attribute field must be a valid email address.',
+    'ends_with' => 'The :attribute field must end with one of the following: :values.',
+    'enum' => 'The selected :attribute is invalid.',
+    'exists' => 'The selected :attribute is invalid.',
+    'extensions' => 'The :attribute field must have one of the following extensions: :values.',
+    'file' => 'The :attribute field must be a file.',
+    'filled' => 'The :attribute field must have a value.',
+    'gt' => [
+        'array' => 'The :attribute field must have more than :value items.',
+        'file' => 'The :attribute field must be greater than :value kilobytes.',
+        'numeric' => 'The :attribute field must be greater than :value.',
+        'string' => 'The :attribute field must be greater than :value characters.',
+    ],
+    'gte' => [
+        'array' => 'The :attribute field must have :value items or more.',
+        'file' => 'The :attribute field must be greater than or equal to :value kilobytes.',
+        'numeric' => 'The :attribute field must be greater than or equal to :value.',
+        'string' => 'The :attribute field must be greater than or equal to :value characters.',
+    ],
+    'hex_color' => 'The :attribute field must be a valid hexadecimal color.',
+    'image' => 'The :attribute field must be an image.',
+    'in' => 'The selected :attribute is invalid.',
+    'in_array' => 'The :attribute field must exist in :other.',
+    'integer' => 'The :attribute field must be an integer.',
+    'ip' => 'The :attribute field must be a valid IP address.',
+    'ipv4' => 'The :attribute field must be a valid IPv4 address.',
+    'ipv6' => 'The :attribute field must be a valid IPv6 address.',
+    'json' => 'The :attribute field must be a valid JSON string.',
+    'list' => 'The :attribute field must be a list.',
+    'lowercase' => 'The :attribute field must be lowercase.',
+    'lt' => [
+        'array' => 'The :attribute field must have less than :value items.',
+        'file' => 'The :attribute field must be less than :value kilobytes.',
+        'numeric' => 'The :attribute field must be less than :value.',
+        'string' => 'The :attribute field must be less than :value characters.',
+    ],
+    'lte' => [
+        'array' => 'The :attribute field must not have more than :value items.',
+        'file' => 'The :attribute field must be less than or equal to :value kilobytes.',
+        'numeric' => 'The :attribute field must be less than or equal to :value.',
+        'string' => 'The :attribute field must be less than or equal to :value characters.',
+    ],
+    'mac_address' => 'The :attribute field must be a valid MAC address.',
+    'max' => [
+        'array' => 'The :attribute field must not have more than :max items.',
+        'file' => 'The :attribute field must not be greater than :max kilobytes.',
+        'numeric' => 'The :attribute field must not be greater than :max.',
+        'string' => 'The :attribute field must not be greater than :max characters.',
+    ],
+    'max_digits' => 'The :attribute field must not have more than :max digits.',
+    'mimes' => 'The :attribute field must be a file of type: :values.',
+    'mimetypes' => 'The :attribute field must be a file of type: :values.',
+    'min' => [
+        'array' => 'The :attribute field must have at least :min items.',
+        'file' => 'The :attribute field must be at least :min kilobytes.',
+        'numeric' => 'The :attribute field must be at least :min.',
+        'string' => 'The :attribute field must be at least :min characters.',
+    ],
+    'min_digits' => 'The :attribute field must have at least :min digits.',
+    'missing' => 'The :attribute field must be missing.',
+    'missing_if' => 'The :attribute field must be missing when :other is :value.',
+    'missing_unless' => 'The :attribute field must be missing unless :other is :value.',
+    'missing_with' => 'The :attribute field must be missing when :values is present.',
+    'missing_with_all' => 'The :attribute field must be missing when :values are present.',
+    'multiple_of' => 'The :attribute field must be a multiple of :value.',
+    'not_in' => 'The selected :attribute is invalid.',
+    'not_regex' => 'The :attribute field format is invalid.',
+    'numeric' => 'The :attribute field must be a number.',
+    'password' => [
+        'letters' => 'The :attribute field must contain at least one letter.',
+        'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.',
+        'numbers' => 'The :attribute field must contain at least one number.',
+        'symbols' => 'The :attribute field must contain at least one symbol.',
+        'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
+    ],
+    'present' => 'The :attribute field must be present.',
+    'present_if' => 'The :attribute field must be present when :other is :value.',
+    'present_unless' => 'The :attribute field must be present unless :other is :value.',
+    'present_with' => 'The :attribute field must be present when :values is present.',
+    'present_with_all' => 'The :attribute field must be present when :values are present.',
+    'prohibited' => 'The :attribute field is prohibited.',
+    'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
+    'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
+    'prohibits' => 'The :attribute field prohibits :other from being present.',
+    'regex' => 'The :attribute field format is invalid.',
+    'required' => 'The :attribute field is required.',
+    'required_array_keys' => 'The :attribute field must contain entries for: :values.',
+    'required_if' => 'The :attribute field is required when :other is :value.',
+    'required_if_accepted' => 'The :attribute field is required when :other is accepted.',
+    'required_if_declined' => 'The :attribute field is required when :other is declined.',
+    'required_unless' => 'The :attribute field is required unless :other is in :values.',
+    'required_with' => 'The :attribute field is required when :values is present.',
+    'required_with_all' => 'The :attribute field is required when :values are present.',
+    'required_without' => 'The :attribute field is required when :values is not present.',
+    'required_without_all' => 'The :attribute field is required when none of :values are present.',
+    'same' => 'The :attribute field must match :other.',
+    'size' => [
+        'array' => 'The :attribute field must contain :size items.',
+        'file' => 'The :attribute field must be :size kilobytes.',
+        'numeric' => 'The :attribute field must be :size.',
+        'string' => 'The :attribute field must be :size characters.',
+    ],
+    'starts_with' => 'The :attribute field must start with one of the following: :values.',
+    'string' => 'The :attribute field must be a string.',
+    'timezone' => 'The :attribute field must be a valid timezone.',
+    'unique' => 'The :attribute has already been taken.',
+    'uploaded' => 'The :attribute failed to upload.',
+    'uppercase' => 'The :attribute field must be uppercase.',
+    'url' => 'The :attribute field must be a valid URL.',
+    'ulid' => 'The :attribute field must be a valid ULID.',
+    'uuid' => 'The :attribute field must be a valid UUID.',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify custom validation messages for attributes using the
+    | convention "attribute.rule" to name the lines. This makes it quick to
+    | specify a specific custom language line for a given attribute rule.
+    |
+    */
+
+    'custom' => [
+        'attribute-name' => [
+            'rule-name' => 'custom-message',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Attributes
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used to swap our attribute placeholder
+    | with something more reader friendly such as "E-Mail Address" instead
+    | of "email". This simply helps us make our message more expressive.
+    |
+    */
+
+    'attributes' => [],
+
+];
diff --git a/package-lock.json b/package-lock.json
index 9cf7ab2..8385ab9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,11 @@
                 "tailwindcss": "^4.0.7",
                 "vite": "^7.0.4"
             },
+            "devDependencies": {
+                "cross-env": "^10.1.0",
+                "daisyui": "^5.5.19",
+                "dotenv": "^17.4.2"
+            },
             "optionalDependencies": {
                 "@rollup/rollup-linux-x64-gnu": "4.9.5",
                 "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
@@ -32,6 +37,12 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/@epic-web/invariant": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
+            "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
+            "dev": true
+        },
         "node_modules/@esbuild/aix-ppc64": {
             "version": "0.25.8",
             "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -1272,6 +1283,46 @@
                 "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
             }
         },
+        "node_modules/cross-env": {
+            "version": "10.1.0",
+            "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
+            "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
+            "dev": true,
+            "dependencies": {
+                "@epic-web/invariant": "^1.0.0",
+                "cross-spawn": "^7.0.6"
+            },
+            "bin": {
+                "cross-env": "dist/bin/cross-env.js",
+                "cross-env-shell": "dist/bin/cross-env-shell.js"
+            },
+            "engines": {
+                "node": ">=20"
+            }
+        },
+        "node_modules/cross-spawn": {
+            "version": "7.0.6",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+            "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+            "dev": true,
+            "dependencies": {
+                "path-key": "^3.1.0",
+                "shebang-command": "^2.0.0",
+                "which": "^2.0.1"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/daisyui": {
+            "version": "5.5.19",
+            "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.19.tgz",
+            "integrity": "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/saadeghi/daisyui?sponsor=1"
+            }
+        },
         "node_modules/delayed-stream": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -1290,6 +1341,18 @@
                 "node": ">=8"
             }
         },
+        "node_modules/dotenv": {
+            "version": "17.4.2",
+            "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+            "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+            "dev": true,
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://dotenvx.com"
+            }
+        },
         "node_modules/dunder-proto": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1634,6 +1697,12 @@
                 "node": ">=8"
             }
         },
+        "node_modules/isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+            "dev": true
+        },
         "node_modules/jiti": {
             "version": "2.5.1",
             "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
@@ -2004,6 +2073,15 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/path-key": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/picocolors": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2132,6 +2210,27 @@
                 "tslib": "^2.1.0"
             }
         },
+        "node_modules/shebang-command": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+            "dev": true,
+            "dependencies": {
+                "shebang-regex": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/shebang-regex": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/shell-quote": {
             "version": "1.8.3",
             "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
@@ -2383,6 +2482,21 @@
                 "url": "https://github.com/sponsors/jonschlinkert"
             }
         },
+        "node_modules/which": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+            "dev": true,
+            "dependencies": {
+                "isexe": "^2.0.0"
+            },
+            "bin": {
+                "node-which": "bin/node-which"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
         "node_modules/wrap-ansi": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index 688bea8..07a99cd 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,10 @@
     "type": "module",
     "scripts": {
         "build": "vite build",
-        "dev": "vite"
+        "dev": "vite",
+        "build:vades": "cross-env SITE=vades.dev vite build",
+        "build:myprompties": "cross-env SITE=mypromties.com vite build",
+        "build:all": "npm run build:vades && npm run build:myprompties"
     },
     "dependencies": {
         "@tailwindcss/vite": "^4.1.11",
@@ -19,5 +22,10 @@
         "@rollup/rollup-linux-x64-gnu": "4.9.5",
         "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
         "lightningcss-linux-x64-gnu": "^1.29.1"
+    },
+    "devDependencies": {
+        "cross-env": "^10.1.0",
+        "daisyui": "^5.5.19",
+        "dotenv": "^17.4.2"
     }
 }
diff --git a/public/robots.txt b/public/robots.txt
index eb05362..d1077f7 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,3 @@
 User-agent: *
-Disallow:
+Disallow: /build/
+Disallow: /storage/
\ No newline at end of file
diff --git a/resources/css/app.css b/resources/css/app.css
index d4b5078..c3c3dd1 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -1 +1,4 @@
-@import 'tailwindcss';
+@import './theme.css'; /* By Sheaf.dev */
+
+
+ /*@custom-variant dark (&:where(.dark, .dark *)); *//* By Sheaf.dev */
diff --git a/resources/css/default/app.css b/resources/css/default/app.css
new file mode 100644
index 0000000..c5cc7d6
--- /dev/null
+++ b/resources/css/default/app.css
@@ -0,0 +1,32 @@
+@import 'tailwindcss';
+@plugin "daisyui";
+@plugin "daisyui/theme" {
+    name: "light";
+    --color-primary: #A21C13;
+    --color-primary-content: #ffffff;
+    --color-secondary: #F79A0F;
+}
+
+@import './theme.css';
+@import './utility.css';
+@import '../shared/typo.css';
+@import '../shared/form.css';
+@import '../shared/header.css';
+@import '../shared/layout.css';
+@import '../shared/footer.css';
+@import '../shared/drawer.css';
+@import '../shared/grid.css';
+@import '../shared/list.css';
+@import '../shared/jumbotron.css';
+@import '../shared/page-header.css';
+@import '../shared/dropdown.css';
+@import '../shared/card.css';
+@import '../shared/list.css';
+@import '../shared/badge.css';
+@import '../shared/panel.css';
+@import '../shared/prev-next.css';
+@import '../shared/iframe.css';
+@import '../shared/button.css';
+@import '../shared/icon.css';
+@import './custom.css';
+
diff --git a/resources/css/default/custom.css b/resources/css/default/custom.css
new file mode 100644
index 0000000..c3ef99c
--- /dev/null
+++ b/resources/css/default/custom.css
@@ -0,0 +1,190 @@
+body {
+
+    @apply bg-bcg-body;
+}
+
+/* region Typo */
+h1, h2, h3, h4, h5, h6, p {
+    @apply font-normal break-words;
+}
+
+.content-wrapper, .page-header {
+    h1 {
+        @apply text-4xl text-heading mb-4 font-serif;
+    }
+
+    h2 {
+        @apply text-2xl text-heading mt-3;
+    }
+
+    h3 {
+        @apply text-xl text-heading mb-1 font-normal;
+    }
+
+    p {
+        @apply leading-[1.5] mb-6;
+    }
+}
+
+/* endregion Typo */
+
+/* region Header */
+.my-header-stripe {
+    height: 5px;
+    background: repeating-linear-gradient(
+        90deg,
+        #fde9e7 0px, #fde9e7 10%,
+        #fabcb7 10%, #fabcb7 20%,
+        #f46358 20%, #f46358 30%,
+        #f79088 30%, #f79088 40%,
+        #d71d0f 40%, #d71d0f 50%,
+        #f03728 50%, #f03728 60%,
+        #f79088 60%, #f79088 70%,
+        #f46358 70%, #f46358 80%,
+        #fabcb7 80%, #fabcb7 90%,
+        #fde9e7 90%, #fde9e7 100%
+    );
+}
+
+.my-header {
+    /*@apply bg-bcg-primary  border-bor-primary/10;*/
+    @apply w-full fixed top-0 left-0  z-[2100] h-20 bg-white;
+    @apply text-header ;
+    /*top: 5px !important;*/
+    box-shadow: 0 3px 10px 0 rgba(50, 50, 50, 0.2);
+
+
+}
+
+.my-header-logo {
+    svg {
+        @apply h-[1.8rem] w-auto lg:h-[2.2rem];
+    }
+}
+
+.my-header-slogan {
+    @apply text-sm text-header/80 italic;
+}
+
+.my-nav-list-item {
+    @apply uppercase pl-6;
+
+    &:hover {
+        @apply underline text-link-hover;
+    }
+
+    .text-base {
+        @apply text-brand-primary;
+    }
+}
+
+.my-header-delimiter {
+    @apply pt-20;
+}
+
+/*@media only screen and (min-width: 1200px) {
+    .my-header {
+        @apply h-28 px-8 py-4;
+    }
+}*/
+
+/* endregion Header */
+
+/* region Footer */
+.my-footer {
+    @apply text-footer text-sm mt-8  border-t border-bor-primary/10 dark:border-white/10;
+
+    a {
+        @apply no-underline font-normal text-sm text-footer hover:text-footer-muted hover:underline;
+    }
+}
+
+/* endregion Footer */
+
+/* region Jumbotron */
+.my-jumbotron {
+    @apply pt-6 mb-8;
+    /* >section {
+         @apply border-b border-black/10 ;
+     }*/
+}
+
+/* endregion Jumbotron */
+
+/* region Homepage */
+.my-home-hero {
+    @apply flex items-center justify-between;
+
+    > div {
+        @apply w-1/2;
+    }
+
+    > div:last-child {
+        @apply flex justify-end;
+    }
+
+    h1 {
+        @apply text-heading sm:text-[2.8rem] font-serif sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+    }
+
+    p {
+        @apply text-[1.4rem] my-8;
+    }
+
+    .home-hero-svg svg {
+        @apply h-[200px] md:h-[400px] w-full ;
+    }
+
+    /* h1{
+         @apply text-heading sm:text-[2.8rem] font-serif sm:tracking-[1rem] sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+     }*/
+    /*> div{
+        @apply text-[1.4rem] my-8;
+    }*/
+
+}
+
+.my-home-feature {
+    @apply bg-base-100 w-96 shadow-sm;
+
+    .card-body {
+        @apply items-center text-center;
+
+        .my-icon {
+            svg {
+                @apply w-[46px] h-[46px];
+            }
+        }
+    }
+
+
+}
+
+.my-homepage-section {
+    @apply mt-8;
+
+    > h2 {
+        @apply text-2xl text-heading uppercase mb-4;
+    }
+}
+
+.pages-contact{
+    .content-container{
+        @apply flex flex-wrap gap-4;
+    }
+
+    .content-container > * {
+        flex: 1 1 0;
+        min-width: 0;
+    }
+}
+.my-card{
+    @apply rounded-sm;
+    .my-card-header{
+        img{
+            @apply rounded-sm max-h-[300px] w-full object-cover object-center;
+        }
+    }
+
+}
+
diff --git a/resources/css/default/theme.css b/resources/css/default/theme.css
new file mode 100644
index 0000000..1879504
--- /dev/null
+++ b/resources/css/default/theme.css
@@ -0,0 +1,110 @@
+
+@theme {
+
+
+    --font-size-base: 1.2rem;
+    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+    'Segoe UI Symbol', 'Noto Color Emoji';
+    --color-brand-primary: #A21C13;
+    --color-brand-primary-muted: #f46358;
+    --color-brand-secondary: #F79A0F;
+    --color-brand-secondary-muted: #faca84;
+    --color-base: #444444;
+    --color-muted: #696969;
+    --color-inverted: #e9ebfc;
+    --color-accent: #000000;
+    --color-heading: #A21C13;
+    --color-h1: #A21C13;
+    --color-link: #A21C13;
+    --color-link-hover: #f46358;
+    --color-btn: #A21C13;
+    --color-btn-hover: #A3160B;
+    --color-input: #000000;
+    --color-input-focus: #A21C13;
+    --color-header: #A21C13;
+    --color-header-muted: #f46358;
+    --color-footer:  #A21C13;
+    --color-footer-muted: #f46358;
+    --color-supplementary: #091843;
+    --color-supplementary-muted: #0f2970;
+    --color-info: #1e429f;
+    --color-success: #03543f;
+    --color-warning: #723b13;
+    --color-danger: #9b1c1c;
+    --color-featured: #0a0a0a;
+    --color-blog: #0a0a0a;
+    --color-place: #0a0a0a;
+    --color-album: #0a0a0a;
+    --color-bor-base: #FEF9F2;
+    --color-bor-muted: #f4f6fd;
+    --color-bor-primary: #A3160B;
+    --color-bor-secondary:#F79A0F;
+    --color-bor-btn: #dbdbdb;
+    --color-bor-input: #dbdbdb;
+    --color-bor-input-focus: #A3160B;
+    --color-bor-footer:#fde9e7;
+    --color-bor-supplementary: #f4f6fd;
+    --color-bor-info: #a4cafe;
+    --color-bor-success: #84e1bc;
+    --color-bor-warning: #faca15;
+    --color-bor-danger: #f8b4b4;
+    --color-bcg-body: #FFFFFF;
+    --color-bcg-base: #F7F8F2;
+    --color-bcg-primary:#A3160B;
+    --color-bcg-primary-muted: #bf5e5e;
+    --color-bcg-secondary:#F69A13;
+    --color-bcg-secondary-muted:#faca84;
+    --color-bcg-btn: #F7F8F2;
+    --color-bcg-btn-hover: #FFFFFF;
+    --color-bcg-input: #fef4e6;
+    --color-bcg-info: #ebf5ff;
+    --color-bcg-success: #f3faf7;
+    --color-bcg-warning: #fdfdea;
+    --color-bcg-danger: #fdf2f2;
+    --color-bcg-accent: #e9eefc;
+    --color-bcg-header: #FFFFFF;
+    --color-bcg-jumbotron: #f4e9d4;
+    --color-bcg-supplementary: #F7F8F2;
+    --color-bcg-footer: #FFFFFF;
+    --color-bcg-card: #F7F8F2;
+    --color-bcg-featured: #F7F8F2;
+    --color-bcg-article: #fef4e6;
+    --color-bcg-blog: #F7F8F2;
+    --color-bcg-place: #F7F8F2;
+    --color-bcg-album: #F7F8F2;
+    --color-bcg-form: #f4e9d4;
+    --color-icon-primary: #A21C13;
+    --color-icon-secondary: #F79A0F;
+
+    /* * SHEAF UI color scheme */
+    /* --color-primary: var(--color-red-500);
+     --color-primary-content: var(--color-blue-700);
+     --color-primary-fg: var(--color-blue-600);*/
+
+    --radius-field: 0.25rem;
+    --radius-box: 0.5rem;
+}
+
+
+/*
+@layer theme {
+    .dark {
+        --color-primary: var(--color-white);
+        --color-primary-content: var(--color-white);
+        --color-primary-fg:  var(--color-neutral-800);
+    }
+}*/
+
+/*@custom-variant dark (&:where(.dark, .dark *));*/
+/* DaisyUI */
+/*:root {
+    !* v5 Variable Name *!
+    --color-primary: #A21C13;
+    --color-secondary: #F79A0F;
+}*/
+/*:root {
+    color-scheme: light !important;
+}*/
+/* Changes dark strategy to look for a .dark class instead of system settings */
+@variant dark (&:where(.dark, .dark *));
+
diff --git a/resources/css/default/utility.css b/resources/css/default/utility.css
new file mode 100644
index 0000000..ca51e9a
--- /dev/null
+++ b/resources/css/default/utility.css
@@ -0,0 +1,7 @@
+
+@utility container {
+    margin-inline: auto;
+    width: 100%;
+    max-width: 1200px;
+    padding-inline: 2rem; /* Optional: adds nice inner spacing */
+}
\ No newline at end of file
diff --git a/resources/css/ivnbg/app.css b/resources/css/ivnbg/app.css
new file mode 100644
index 0000000..a7535c8
--- /dev/null
+++ b/resources/css/ivnbg/app.css
@@ -0,0 +1,30 @@
+@import 'tailwindcss';
+@plugin "daisyui";
+@plugin "daisyui/theme" {
+    name: "light";
+    --color-primary: #8B0000;
+    --color-primary-content: #ffffff;
+    --color-secondary: #023859;
+}
+
+@import './theme.css';
+@import './utility.css';
+@import '../shared/typo.css';
+@import '../shared/form.css';
+@import '../shared/header.css';
+@import '../shared/layout.css';
+@import '../shared/footer.css';
+@import '../shared/drawer.css';
+@import '../shared/grid.css';
+@import '../shared/jumbotron.css';
+@import '../shared/page-header.css';
+@import '../shared/dropdown.css';
+@import '../shared/card.css';
+@import '../shared/list.css';
+@import '../shared/badge.css';
+@import '../shared/panel.css';
+@import '../shared/prev-next.css';
+@import '../shared/iframe.css';
+@import '../shared/button.css';
+@import '../shared/icon.css';
+@import './custom.css';
diff --git a/resources/css/ivnbg/custom.css b/resources/css/ivnbg/custom.css
new file mode 100644
index 0000000..e2b1f8e
--- /dev/null
+++ b/resources/css/ivnbg/custom.css
@@ -0,0 +1,128 @@
+
+
+/* region Header */
+.my-header-stripe {
+    height: 5px;
+    background: repeating-linear-gradient(
+        90deg,
+        #fde9e7 0px, #fde9e7 10%,
+        #fabcb7 10%, #fabcb7 20%,
+        #f46358 20%, #f46358 30%,
+        #f79088 30%, #f79088 40%,
+        #d71d0f 40%, #d71d0f 50%,
+        #f03728 50%, #f03728 60%,
+        #f79088 60%, #f79088 70%,
+        #f46358 70%, #f46358 80%,
+        #fabcb7 80%, #fabcb7 90%,
+        #fde9e7 90%, #fde9e7 100%
+    );
+}
+
+.my-header {
+    /*@apply bg-bcg-primary  border-bor-primary/10;*/
+    @apply w-full fixed top-0 left-0  z-[2100] h-20 bg-white;
+    @apply text-header ;
+    /*top: 5px !important;*/
+    box-shadow: 0 3px 10px 0 rgba(50, 50, 50, 0.2);
+
+
+}
+
+.my-header-logo {
+    svg {
+        @apply h-[1.8rem] w-auto lg:h-[2.2rem];
+    }
+}
+
+.my-header-slogan {
+    @apply text-sm text-header/80 italic;
+}
+
+.my-nav-list-item {
+    @apply uppercase pl-6;
+
+    &:hover {
+        @apply underline text-link-hover;
+    }
+
+    .text-base {
+        @apply text-brand-primary;
+    }
+}
+
+.my-header-delimiter {
+    @apply pt-20;
+}
+
+
+.my-home-hero {
+    @apply flex items-center justify-between;
+
+    > div {
+        @apply w-1/2;
+    }
+
+    > div:last-child {
+        @apply flex justify-end;
+    }
+
+    h1 {
+        @apply text-heading sm:text-[2.8rem] font-serif sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+    }
+
+    p {
+        @apply text-[1.4rem] my-8;
+    }
+
+    .home-hero-svg svg {
+        @apply h-[200px] md:h-[400px] w-full ;
+    }
+
+}
+
+.my-home-feature {
+    @apply bg-base-100 w-96 shadow-sm;
+
+    .card-body {
+        @apply items-center text-center;
+
+        .my-icon {
+            svg {
+                @apply w-[46px] h-[46px];
+            }
+        }
+    }
+
+
+}
+
+.my-home-section {
+    @apply mt-8 border-t border-gray-100;
+
+    > h2 {
+        @apply text-2xl text-heading uppercase mb-4 text-center;
+    }
+}
+
+.home{
+    .my-card-title, .my-card-subtitle{
+        @apply text-center;
+    }
+}
+
+.my-grid-place-related{
+    .my-card-title, .my-card-subtitle{
+        @apply text-center;
+    }
+}
+
+.pages-contact{
+    .content-container{
+        @apply flex flex-wrap gap-4;
+    }
+
+    .content-container > * {
+        flex: 1 1 0;
+        min-width: 0;
+    }
+}
diff --git a/resources/css/ivnbg/theme.css b/resources/css/ivnbg/theme.css
new file mode 100644
index 0000000..5aeaa4d
--- /dev/null
+++ b/resources/css/ivnbg/theme.css
@@ -0,0 +1,109 @@
+
+@theme {
+
+
+    --font-size-base: 1.2rem;
+    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+    'Segoe UI Symbol', 'Noto Color Emoji';
+    --color-brand-primary: #8B0000;
+    --color-brand-primary-muted: #bf5e5e;
+    --color-brand-secondary: #023859;
+    --color-brand-secondary-muted: #005d9f;
+    --color-base: #444444;
+    --color-muted: #696969;
+    --color-inverted: #e9ebfc;
+    --color-accent: #000000;
+    --color-heading: #8B0000;
+    --color-h1: #8B0000;
+    --color-link: #8B0000;
+    --color-link-hover: #7f1e1e;
+    --color-btn: #8B0000;
+    --color-btn-hover: #8B0000;
+    --color-input: #000000;
+    --color-input-focus: #7f1e1e;
+    --color-header: #414141;
+    --color-header-muted: #8fa8f0;
+    --color-footer: #F7F3F0;
+    --color-footer-muted: #9f9f9f;
+    --color-supplementary: #091843;
+    --color-supplementary-muted: #0f2970;
+    --color-info: #1e429f;
+    --color-success: #03543f;
+    --color-warning: #723b13;
+    --color-danger: #9b1c1c;
+    --color-featured: #0a0a0a;
+    --color-blog: #0a0a0a;
+    --color-place: #0a0a0a;
+    --color-album: #0a0a0a;
+    --color-bor-base: #FEF9F2;
+    --color-bor-muted: #f4f6fd;
+    --color-bor-primary: #8B0000;
+    --color-bor-secondary:#023859;
+    --color-bor-btn: #dbdbdb;
+    --color-bor-input: #dbdbdb;
+    --color-bor-input-focus: #A3160B;
+    --color-bor-footer:#fde9e7;
+    --color-bor-supplementary: #f4f6fd;
+    --color-bor-info: #a4cafe;
+    --color-bor-success: #84e1bc;
+    --color-bor-warning: #faca15;
+    --color-bor-danger: #f8b4b4;
+    --color-bcg-body: #FFFFFF;
+    --color-bcg-base: #F7F8F2;
+    --color-bcg-primary:#8B0000;
+    --color-bcg-primary-muted: #bf5e5e;
+    --color-bcg-secondary:#023859;
+    --color-bcg-secondary-muted:#005d9f;
+    --color-bcg-btn: #F7F8F2;
+    --color-bcg-btn-hover: #FFFFFF;
+    --color-bcg-input: #fef4e6;
+    --color-bcg-info: #ebf5ff;
+    --color-bcg-success: #f3faf7;
+    --color-bcg-warning: #fdfdea;
+    --color-bcg-danger: #fdf2f2;
+    --color-bcg-accent: #e9eefc;
+    --color-bcg-header: #FFFFFF;
+    --color-bcg-jumbotron: #f4e9d4;
+    --color-bcg-supplementary: #F7F8F2;
+    --color-bcg-footer: #023859;
+    --color-bcg-card: #F7F8F2;
+    --color-bcg-featured: #F7F8F2;
+    --color-bcg-article: #fef4e6;
+    --color-bcg-blog: #F7F8F2;
+    --color-bcg-place: #F7F8F2;
+    --color-bcg-album: #F7F8F2;
+    --color-bcg-form: #f4e9d4;
+    --color-icon-primary: #8B0000;
+    --color-icon-secondary: #023859;
+
+    /* * SHEAF UI color scheme */
+    /* --color-primary: var(--color-red-500);
+     --color-primary-content: var(--color-blue-700);
+     --color-primary-fg: var(--color-blue-600);*/
+
+    --radius-field: 0.25rem;
+    --radius-box: 0.5rem;
+}
+
+
+/*
+@layer theme {
+    .dark {
+        --color-primary: var(--color-white);
+        --color-primary-content: var(--color-white);
+        --color-primary-fg:  var(--color-neutral-800);
+    }
+}*/
+
+/*@custom-variant dark (&:where(.dark, .dark *));*/
+/* DaisyUI */
+/*:root {
+    !* v5 Variable Name *!
+    --color-primary: #A21C13;
+    --color-secondary: #F79A0F;
+}*/
+/*:root {
+    color-scheme: light !important;
+}*/
+/* Changes dark strategy to look for a .dark class instead of system settings */
+@variant dark (&:where(.dark, .dark *));
diff --git a/resources/css/ivnbg/utility.css b/resources/css/ivnbg/utility.css
new file mode 100644
index 0000000..ca51e9a
--- /dev/null
+++ b/resources/css/ivnbg/utility.css
@@ -0,0 +1,7 @@
+
+@utility container {
+    margin-inline: auto;
+    width: 100%;
+    max-width: 1200px;
+    padding-inline: 2rem; /* Optional: adds nice inner spacing */
+}
\ No newline at end of file
diff --git a/resources/css/martinvach/app.css b/resources/css/martinvach/app.css
new file mode 100644
index 0000000..27821ee
--- /dev/null
+++ b/resources/css/martinvach/app.css
@@ -0,0 +1,30 @@
+@import 'tailwindcss';
+@plugin "daisyui";
+@plugin "daisyui/theme" {
+    name: "light";
+    --color-primary: #023859;
+    --color-primary-content: #ffffff;
+    --color-secondary: #696969;
+}
+
+@import './theme.css';
+@import './utility.css';
+@import '../shared/typo.css';
+@import '../shared/form.css';
+@import '../shared/header.css';
+@import '../shared/layout.css';
+@import '../shared/footer.css';
+@import '../shared/drawer.css';
+@import '../shared/grid.css';
+@import '../shared/jumbotron.css';
+@import '../shared/page-header.css';
+@import '../shared/dropdown.css';
+@import '../shared/card.css';
+@import '../shared/list.css';
+@import '../shared/badge.css';
+@import '../shared/panel.css';
+@import '../shared/prev-next.css';
+@import '../shared/iframe.css';
+@import '../shared/button.css';
+@import '../shared/icon.css';
+@import './custom.css';
diff --git a/resources/css/martinvach/custom.css b/resources/css/martinvach/custom.css
new file mode 100644
index 0000000..00b761e
--- /dev/null
+++ b/resources/css/martinvach/custom.css
@@ -0,0 +1,132 @@
+
+
+/* region Header */
+.my-header-stripe {
+    height: 5px;
+    background: repeating-linear-gradient(
+        90deg,
+        #f5f5f5 0px, #f5f5f5 10%,
+        #e0e0e0 10%, #e0e0e0 20%,
+        #a0a0a0 20%, #a0a0a0 30%,
+        #c2c2c2 30%, #c2c2c2 40%,
+        #4a4a4a 40%, #4a4a4a 50%,
+        #6e6e6e 50%, #6e6e6e 60%,
+        #c2c2c2 60%, #c2c2c2 70%,
+        #a0a0a0 70%, #a0a0a0 80%,
+        #e0e0e0 80%, #e0e0e0 90%,
+        #f5f5f5 90%, #f5f5f5 100%
+    );
+}
+
+.my-header {
+    /*@apply bg-bcg-primary  border-bor-primary/10;*/
+    @apply w-full fixed top-0 left-0  z-[2100] h-20 bg-white;
+    @apply text-header ;
+    /*top: 5px !important;*/
+    box-shadow: 0 3px 10px 0 rgba(50, 50, 50, 0.2);
+
+
+}
+
+.my-header-logo {
+    svg {
+        @apply h-[2.2rem] w-auto lg:h-[3.2rem];
+    }
+}
+
+.my-header-slogan {
+    @apply text-sm text-header/80 italic;
+}
+
+.my-nav-list-item {
+    @apply uppercase pl-6;
+
+    &:hover {
+        @apply underline text-link-hover;
+    }
+
+    .text-base {
+        @apply text-brand-primary;
+    }
+}
+
+.my-header-delimiter {
+    @apply pt-20;
+}
+
+.my-footer {
+    @apply border-t border-bor-base;
+}
+
+
+.my-home-hero {
+    @apply flex items-center justify-between;
+
+    > div {
+        @apply w-1/2;
+    }
+
+    > div:last-child {
+        @apply flex justify-end;
+    }
+
+    h1 {
+        @apply text-heading sm:text-[2.8rem] font-serif sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+    }
+
+    p {
+        @apply text-[1.4rem] my-8;
+    }
+
+    .home-hero-svg svg {
+        @apply h-[200px] md:h-[400px] w-full ;
+    }
+
+}
+
+.my-home-feature {
+    @apply bg-base-100 w-96 shadow-sm;
+
+    .card-body {
+        @apply items-center text-center;
+
+        .my-icon {
+            svg {
+                @apply w-[46px] h-[46px];
+            }
+        }
+    }
+
+
+}
+
+.my-home-section {
+    @apply mt-8 border-t border-gray-100;
+
+    > h2 {
+        @apply text-2xl text-heading uppercase mb-4 text-center;
+    }
+}
+
+.home{
+    .my-card-title, .my-card-subtitle{
+        @apply text-center;
+    }
+}
+
+.my-grid-place-related{
+    .my-card-title, .my-card-subtitle{
+        @apply text-center;
+    }
+}
+
+.pages-contact{
+    .content-container{
+        @apply flex flex-wrap gap-4;
+    }
+
+    .content-container > * {
+        flex: 1 1 0;
+        min-width: 0;
+    }
+}
diff --git a/resources/css/martinvach/theme.css b/resources/css/martinvach/theme.css
new file mode 100644
index 0000000..7d99418
--- /dev/null
+++ b/resources/css/martinvach/theme.css
@@ -0,0 +1,82 @@
+
+@theme {
+
+
+    --font-size-base: 1.2rem;
+    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+    'Segoe UI Symbol', 'Noto Color Emoji';
+    --color-brand-primary: #023859;
+    --color-brand-primary-muted: #005d9f;
+    --color-brand-secondary: #023859;
+    --color-brand-secondary-muted: #005d9f;
+    --color-base: #444444;
+    --color-muted: #696969;
+    --color-inverted: #e9ebfc;
+    --color-accent: #000000;
+    --color-heading: #444444;
+    --color-h1: #444444;
+    --color-link: #005d9f;
+    --color-link-hover:#023859;
+    --color-btn: #023859;
+    --color-btn-hover: #005d9f;
+    --color-input: #000000;
+    --color-input-focus: #005d9f;
+    --color-header: #414141;
+    --color-header-muted: #8fa8f0;
+    --color-footer:  #414141;
+    --color-footer-muted: #9f9f9f;
+    --color-supplementary: #091843;
+    --color-supplementary-muted: #0f2970;
+    --color-info: #1e429f;
+    --color-success: #03543f;
+    --color-warning: #723b13;
+    --color-danger: #9b1c1c;
+    --color-featured: #0a0a0a;
+    --color-blog: #0a0a0a;
+    --color-place: #0a0a0a;
+    --color-album: #0a0a0a;
+    --color-bor-base: #d7d7d7;
+    --color-bor-muted: #f4f6fd;
+    --color-bor-primary: #005d9f;
+    --color-bor-secondary:#023859;
+    --color-bor-btn: #dbdbdb;
+    --color-bor-input: #dbdbdb;
+    --color-bor-input-focus: #005d9f;
+    --color-bor-footer:#fde9e7;
+    --color-bor-supplementary: #f4f6fd;
+    --color-bor-info: #a4cafe;
+    --color-bor-success: #84e1bc;
+    --color-bor-warning: #faca15;
+    --color-bor-danger: #f8b4b4;
+    --color-bcg-body: #FFFFFF;
+    --color-bcg-base: #F7F8F2;
+    --color-bcg-primary:#005d9f;
+    --color-bcg-primary-muted: #299ff1;
+    --color-bcg-secondary:#023859;
+    --color-bcg-secondary-muted:#005d9f;
+    --color-bcg-btn: #F7F8F2;
+    --color-bcg-btn-hover: #FFFFFF;
+    --color-bcg-input: #fef4e6;
+    --color-bcg-info: #ebf5ff;
+    --color-bcg-success: #f3faf7;
+    --color-bcg-warning: #fdfdea;
+    --color-bcg-danger: #fdf2f2;
+    --color-bcg-accent: #e9eefc;
+    --color-bcg-header: #FFFFFF;
+    --color-bcg-jumbotron: #eaeaea;
+    --color-bcg-supplementary: #F7F8F2;
+    --color-bcg-footer: #fff;
+    --color-bcg-card: #F7F8F2;
+    --color-bcg-featured: #F7F8F2;
+    --color-bcg-article: #fef4e6;
+    --color-bcg-blog: #F7F8F2;
+    --color-bcg-place: #F7F8F2;
+    --color-bcg-album: #F7F8F2;
+    --color-bcg-form: #f3f3f3;
+    --color-icon-primary: #023859;
+    --color-icon-secondary: #023859;
+
+    --radius-field: 0.25rem;
+    --radius-box: 0.5rem;
+}
+@variant dark (&:where(.dark, .dark *));
diff --git a/resources/css/martinvach/utility.css b/resources/css/martinvach/utility.css
new file mode 100644
index 0000000..ca51e9a
--- /dev/null
+++ b/resources/css/martinvach/utility.css
@@ -0,0 +1,7 @@
+
+@utility container {
+    margin-inline: auto;
+    width: 100%;
+    max-width: 1200px;
+    padding-inline: 2rem; /* Optional: adds nice inner spacing */
+}
\ No newline at end of file
diff --git a/resources/css/shared/alert.css b/resources/css/shared/alert.css
new file mode 100644
index 0000000..0274c79
--- /dev/null
+++ b/resources/css/shared/alert.css
@@ -0,0 +1,22 @@
+
+/* region Alert */
+.alert {
+    @apply p-4 text-sm text-gray-800 rounded-lg bg-gray-50 font-medium mb-4;
+}
+.alert.is-info {
+    @apply text-blue-800 bg-blue-50 ;
+}
+
+.alert.is-danger {
+    @apply text-red-800 bg-red-50 ;
+}
+
+.alert.is-success {
+    @apply text-green-800 bg-green-50 ;
+}
+.alert.is-warning {
+    @apply text-yellow-800 bg-yellow-50 ;
+}
+
+
+/* endregion */
\ No newline at end of file
diff --git a/resources/css/shared/badge.css b/resources/css/shared/badge.css
new file mode 100644
index 0000000..5c515b1
--- /dev/null
+++ b/resources/css/shared/badge.css
@@ -0,0 +1,5 @@
+
+
+.my-badge {
+    @apply badge badge-soft badge-primary;
+}
\ No newline at end of file
diff --git a/resources/css/shared/button.css b/resources/css/shared/button.css
new file mode 100644
index 0000000..e3c905d
--- /dev/null
+++ b/resources/css/shared/button.css
@@ -0,0 +1,15 @@
+
+.my-btn-raquo::after {
+    content: '\00BB';
+    @apply inline-block ml-1;
+}
+
+.my-btn-laquo::before {
+    content: '\00AB';
+    @apply inline-block mr-1;
+}
+
+.my-btn-triangle-down::after {
+    content: '\25BE';
+    @apply inline-block ml-1;
+}
diff --git a/resources/css/shared/card.css b/resources/css/shared/card.css
new file mode 100644
index 0000000..ab82ad0
--- /dev/null
+++ b/resources/css/shared/card.css
@@ -0,0 +1,72 @@
+/* region Card Component Styles */
+.my-card {
+    @apply flex flex-col justify-between h-full bg-bcg-card dark:bg-neutral-900 border border-black/10 dark:border-white/10 border-bor-base dark:hover:bg-neutral-800  mb-3;
+}
+.my-card-footer {
+    @apply mt-auto text-center pb-2;
+}
+.my-card-body{
+    @apply flex-grow px-6 text-sm ;
+}
+
+.my-card-img{
+    @apply mr-auto ml-auto object-cover mb-4;
+}
+
+.my-card-title{
+    @apply  text-lg leading-[1.2] mb-2 text-h1;
+}
+.my-card-subtitle{
+    @apply mb-2 italic;
+}
+
+.my-card-date{
+    @apply text-sm mb-2 italic;
+}
+
+.my-card-info{
+    @apply text-sm mb-3;
+}
+.my-card-excerpt{
+    @apply  mb-3;
+}
+
+.my-card-article{
+    .my-card-img{
+        @apply h-64;
+    }
+}
+/* region Featured Card Styles */
+.my-card-featured{
+    @apply relative overflow-hidden rounded-sm;
+    figure {
+        @apply relative overflow-hidden rounded-sm;
+        img {
+            @apply w-full max-h-64 object-cover cursor-pointer
+            transition-transform duration-300 ease-in-out hover:scale-110
+            rounded-sm;
+        }
+        figcaption {
+            @apply absolute bottom-0 left-0 right-0 p-2 bg-[rgba(255,255,255,0.6)] mt-2 text-center text-sm;
+        }
+    }
+}
+/* endregion Featured Card Styles */
+
+
+/* region Gallery Card Styles */
+.my-card-gallery{
+    @apply relative overflow-hidden rounded-sm;
+    figure {
+        @apply relative overflow-hidden rounded-sm;
+        img {
+            @apply w-full aspect-square object-cover cursor-pointer
+            transition-transform duration-300 ease-in-out hover:scale-110
+            rounded-sm;
+        }
+        figcaption {
+            @apply absolute bottom-0 left-0 right-0 p-2 bg-[rgba(255,255,255,0.6)] mt-2 text-center text-sm;
+        }
+    }
+}
+/* endregion Gallery Card Styles */
diff --git a/resources/css/shared/drawer.css b/resources/css/shared/drawer.css
new file mode 100644
index 0000000..56e5669
--- /dev/null
+++ b/resources/css/shared/drawer.css
@@ -0,0 +1,20 @@
+
+.my-drawer {
+    .drawer-content {
+        @apply  flex flex-col;
+    }
+    .navbar{
+        @apply  w-full;
+    }
+    .my-drawer-btn-wrapper {
+        @apply lg:hidden;
+    }
+    .my-drawer-menu-horizontal {
+        @apply  hidden flex-none lg:block;
+    }
+    .drawer-side {
+        .menu{
+            @apply  bg-base-200 min-h-full w-80 p-4;
+        }
+    }
+}
diff --git a/resources/css/shared/dropdown.css b/resources/css/shared/dropdown.css
new file mode 100644
index 0000000..9d88185
--- /dev/null
+++ b/resources/css/shared/dropdown.css
@@ -0,0 +1,9 @@
+.dropdown{
+    @apply min-w-32 w-auto rounded-box bg-base-100 shadow-sm;
+}
+.my-search-dropdown{
+    @apply !w-full;
+    .dropdown-content{
+        @apply menu bg-base-100 rounded-box z-1 w-full p-2 shadow-sm  menu;
+    }
+}
diff --git a/resources/css/shared/footer.css b/resources/css/shared/footer.css
new file mode 100644
index 0000000..b93c996
--- /dev/null
+++ b/resources/css/shared/footer.css
@@ -0,0 +1,12 @@
+.my-footer {
+    @apply bg-bcg-footer;
+    .my-footer-container{
+        @apply container mx-auto text-center p-4 xl:px-0 md:flex justify-between items-center text-sm;
+    }
+    .my-footer-brand {
+        @apply hidden md:inline text-sm text-footer;
+    }
+    .my-footer-nav {
+        @apply text-center text-sm flex [&>a]:pl-2 [&>a]:pl-4 [&>a]:text-footer [&>a]:hover:text-footer-muted;
+    }
+}
diff --git a/resources/css/shared/form.css b/resources/css/shared/form.css
new file mode 100644
index 0000000..8aa9edb
--- /dev/null
+++ b/resources/css/shared/form.css
@@ -0,0 +1,30 @@
+
+.my-form {
+    @apply bg-bcg-form rounded-md p-4 mb-4;
+}
+/*.my-fieldset {
+    @apply bg-bcg-jumbotron rounded-md p-4 mb-4;
+}*/
+.my-form-field {
+    @apply mb-4;
+
+    &.has-w-full {
+        @apply w-full;
+
+        & input,
+        & textarea,
+        & label,
+        & span {
+            @apply w-full;
+        }
+    }
+}
+
+.my-form-submit {
+    @apply flex justify-center items-center gap-4;
+}
+
+.my-form-error {
+    @apply block mt-2 text-sm text-red-600 ;
+}
+
diff --git a/resources/css/shared/grid.css b/resources/css/shared/grid.css
new file mode 100644
index 0000000..3514253
--- /dev/null
+++ b/resources/css/shared/grid.css
@@ -0,0 +1,11 @@
+.my-grid-article{
+
+}
+
+.my-grid-place{
+    
+}
+
+.my-grid-gallery{
+    
+}
\ No newline at end of file
diff --git a/resources/css/shared/header.css b/resources/css/shared/header.css
new file mode 100644
index 0000000..ed6f2ce
--- /dev/null
+++ b/resources/css/shared/header.css
@@ -0,0 +1,55 @@
+.my-header-stripe {}
+.my-header{
+    @apply bg-white dark:bg-neutral-900 border-b border-black/10 dark:border-white/10;
+
+}
+.my-header-container{
+    @apply container mx-auto flex justify-between items-center px-4 xl:px-0 h-16;
+}
+.my-header-brand{
+    @apply flex  justify-between items-center gap-6;
+
+}
+
+.my-header-logo {
+    svg {
+        @apply w-12 h-12;
+    }
+}
+.my-header-slogan {
+    @apply hidden lg:inline ;
+}
+
+.header-search{
+    @apply flex-1 mx-4;
+}
+
+.my-header-navbar{
+    @apply lg:flex lg:flex-1;
+}
+
+.my-header-search {
+    @apply flex-1 mx-4;
+}
+
+.my-header-category{
+    @apply hidden lg:inline;
+    .btn{
+        @apply btn-wide btn-outline btn-primary ;
+    }
+
+}
+
+.my-header-nav-sm{
+    @apply flex items-center gap-4 lg:hidden;
+}
+
+
+
+.header-nav{
+    a {
+        &:hover {
+            @apply text-link-hover;
+        }
+    }
+}
diff --git a/resources/css/shared/icon.css b/resources/css/shared/icon.css
new file mode 100644
index 0000000..c7ed6de
--- /dev/null
+++ b/resources/css/shared/icon.css
@@ -0,0 +1,4 @@
+.my-icon svg{
+    @apply stroke-icon-primary;
+
+}
diff --git a/resources/css/shared/iframe.css b/resources/css/shared/iframe.css
new file mode 100644
index 0000000..821c652
--- /dev/null
+++ b/resources/css/shared/iframe.css
@@ -0,0 +1,6 @@
+.my-iframe-wrapper {
+    @apply relative pb-[56.25%] pt-8 h-0 overflow-hidden;
+}
+.my-iframe {
+    @apply absolute top-0 left-0 w-full h-full;
+}
diff --git a/resources/css/shared/jumbotron.css b/resources/css/shared/jumbotron.css
new file mode 100644
index 0000000..7e1f65c
--- /dev/null
+++ b/resources/css/shared/jumbotron.css
@@ -0,0 +1,6 @@
+.my-jumbotron{
+    @apply px-4 xl:px-0 bg-bcg-jumbotron mb-8;
+}
+.my-jumbotron-container{
+    @apply container mx-auto px-4 py-4  lg:px-0;
+}
diff --git a/resources/css/shared/layout.css b/resources/css/shared/layout.css
new file mode 100644
index 0000000..cb390c0
--- /dev/null
+++ b/resources/css/shared/layout.css
@@ -0,0 +1,13 @@
+body {
+    @apply m-0 text-base-content bg-bcg-body;
+}
+#root{
+    @apply min-h-screen flex flex-col;
+}
+
+.my-header-delimiter{}
+
+
+main{
+    @apply container mx-auto mb-auto px-4 xl:px-0 pb-4  dark:bg-neutral-900;
+}
diff --git a/resources/css/shared/list.css b/resources/css/shared/list.css
new file mode 100644
index 0000000..93b1dce
--- /dev/null
+++ b/resources/css/shared/list.css
@@ -0,0 +1,23 @@
+.my-list{
+    .list-row{
+        @apply bg-bcg-card rounded-box shadow-md;
+        .my-list-title{
+            @apply text-lg text-h1 mb-2;
+        }
+        .my-list-subtitle{
+    @apply  text-xs uppercase font-semibold opacity-80 mb-2;
+        }
+        .my-list-date{
+            @apply  text-xs italic opacity-80 mb-2;
+        }
+        .my-list-excerpt{
+
+        }
+        .my-list-img{
+            @apply size-20 rounded-box;
+        }
+    }
+    .my-list-row-highlight{
+    }
+
+}
diff --git a/resources/css/shared/page-header.css b/resources/css/shared/page-header.css
new file mode 100644
index 0000000..e1f5a68
--- /dev/null
+++ b/resources/css/shared/page-header.css
@@ -0,0 +1,15 @@
+.my-page-header {
+    @apply flex gap-4 flex-col md:flex-row md:items-start py-4;
+}
+.my-page-header-title {
+    @apply text-3xl mb-2;
+}
+.my-page-header-subtitle {
+    @apply  text-xl mb-2;
+}
+.my-page-header-description {
+    @apply font-bold my-2;
+}
+.my-page-header-info {
+    @apply text-sm mb-3;
+}
diff --git a/resources/css/shared/panel.css b/resources/css/shared/panel.css
new file mode 100644
index 0000000..d81f75d
--- /dev/null
+++ b/resources/css/shared/panel.css
@@ -0,0 +1,9 @@
+.my-panel{
+    @apply sm:flex sm:flex-row items-start gap-2 md:gap-4 bg-white dark:bg-neutral-900 border border-black/10 dark:border-white/10 border-bor-base rounded-sm dark:hover:bg-neutral-800;
+}
+.my-panel-header{
+    @apply sm:w-1/3;
+}
+.my-panel-body{
+    @apply sm:w-2/3;
+}
diff --git a/resources/css/shared/prev-next.css b/resources/css/shared/prev-next.css
new file mode 100644
index 0000000..e1cb102
--- /dev/null
+++ b/resources/css/shared/prev-next.css
@@ -0,0 +1,8 @@
+.my-prev-next {
+ @apply flex gap-4;
+}
+.my-prev-next-prev {
+    @apply mr-4;
+}
+.my-prev-next-next {
+}
diff --git a/resources/css/shared/search.css b/resources/css/shared/search.css
new file mode 100644
index 0000000..10addfb
--- /dev/null
+++ b/resources/css/shared/search.css
@@ -0,0 +1,13 @@
+/* region Search suggestion */
+.search-suggestions {
+    @apply relative;
+}
+
+.search-suggestions-container {
+    @apply absolute inset-y-0 end-2.5 flex items-center ps-3.5 pointer-events-none;
+}
+.search-suggestions-icon {
+    @apply [&>svg]:text-gray-500;
+}
+
+/* endregion */
\ No newline at end of file
diff --git a/resources/css/shared/supplementary.css b/resources/css/shared/supplementary.css
new file mode 100644
index 0000000..361e0ed
--- /dev/null
+++ b/resources/css/shared/supplementary.css
@@ -0,0 +1,19 @@
+#supplementary {
+    @apply p-4 bg-bcg-supplementary border-t border-bor-supplementary;
+    .supplementary-container {
+        @apply container mx-auto text-center text-sm p-4 md:flex justify-between items-center text-supplementary;
+    }
+    /* region Nav */
+    nav {
+        @apply [&>a]:pl-2;
+        .title{
+            @apply font-bold;
+        }
+        a {
+            @apply text-supplementary;
+            &:hover {
+                @apply text-supplementary-muted;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/resources/css/shared/typo.css b/resources/css/shared/typo.css
new file mode 100644
index 0000000..22070e9
--- /dev/null
+++ b/resources/css/shared/typo.css
@@ -0,0 +1,60 @@
+
+
+h1 {
+  @apply text-4xl text-h1 mb-4 font-serif break-words;
+}
+
+h2 {
+  @apply text-2xl text-heading mt-3 break-words mb-2;
+}
+
+h3 {
+  @apply text-xl text-heading mb-1 break-words;
+}
+
+h4 {
+  @apply text-lg text-heading mb-1 break-words;
+}
+
+h5 {
+  @apply text-base text-heading mb-1 break-words;
+}
+
+h6 {
+  @apply text-sm text-heading mb-1 break-words;
+}
+
+.my-subtitle {
+  @apply text-heading text-lg italic;
+}
+
+.my-perex {
+  @apply text-lg  mb-4 font-serif;
+}
+
+.content-wrapper, .page-header {
+    h1 {
+        @apply text-4xl text-heading mb-4 font-serif;
+    }
+
+    h2 {
+        @apply text-2xl text-heading mt-4 mb-2;
+    }
+
+    h3 {
+        @apply text-xl text-heading mb-1 font-normal;
+    }
+
+    p {
+        @apply leading-[1.5] mb-4;
+    }
+}
+
+main{
+   /* a {
+      @apply text-primary underline hover:text-primary/70 hover:no-underline;
+    }*/
+    p {
+  @apply leading-[1.2rem];
+}
+}
diff --git a/resources/css/shared/utils.css b/resources/css/shared/utils.css
new file mode 100644
index 0000000..0c9183b
--- /dev/null
+++ b/resources/css/shared/utils.css
@@ -0,0 +1,12 @@
+/* region Utils */
+.has-transition{
+    @apply transform transition-transform duration-300 hover:scale-110;
+}
+
+.iframe{
+    @apply relative pb-[56.25%] pt-8 h-0 overflow-hidden;
+}
+.iframe iframe{
+    @apply absolute top-0 left-0 w-full h-full;
+}
+/* endregion */
\ No newline at end of file
diff --git a/resources/css/theme.css b/resources/css/theme.css
new file mode 100644
index 0000000..442446f
--- /dev/null
+++ b/resources/css/theme.css
@@ -0,0 +1,24 @@
+/* By Sheaf.dev */
+@theme inline {
+    /* base color variables */
+
+    /* --color-neutral-900: var(--color-neutral-950); */
+
+    /* primary color varibles */
+    --color-primary: var(--color-neutral-800);
+    --color-primary-content: var(--color-neutral-800);
+    --color-primary-fg: var(--color-white);
+
+    /* radius variables */
+    --radius-field: 0.25rem;
+    --radius-box: 0.5rem;
+}
+
+
+@layer theme {
+    .dark {
+        --color-primary: var(--color-white);
+        --color-primary-content: var(--color-white);
+        --color-primary-fg:  var(--color-neutral-800);
+    }
+}
\ No newline at end of file
diff --git a/resources/css/vades/app.css b/resources/css/vades/app.css
new file mode 100644
index 0000000..76d71e7
--- /dev/null
+++ b/resources/css/vades/app.css
@@ -0,0 +1,31 @@
+@import 'tailwindcss';
+@plugin "daisyui";
+@plugin "daisyui/theme" {
+    name: "light";
+    --color-primary: #A21C13;
+    --color-primary-content: #ffffff;
+    --color-secondary: #F79A0F;
+}
+
+@import './theme.css';
+@import './utility.css';
+@import '../shared/typo.css';
+@import '../shared/form.css';
+@import '../shared/header.css';
+@import '../shared/layout.css';
+@import '../shared/footer.css';
+@import '../shared/drawer.css';
+@import '../shared/grid.css';
+@import '../shared/jumbotron.css';
+@import '../shared/page-header.css';
+@import '../shared/dropdown.css';
+@import '../shared/card.css';
+@import '../shared/list.css';
+@import '../shared/badge.css';
+@import '../shared/panel.css';
+@import '../shared/prev-next.css';
+@import '../shared/iframe.css';
+@import '../shared/button.css';
+@import '../shared/icon.css';
+@import './custom.css';
+
diff --git a/resources/css/vades/custom.css b/resources/css/vades/custom.css
new file mode 100644
index 0000000..039e24b
--- /dev/null
+++ b/resources/css/vades/custom.css
@@ -0,0 +1,180 @@
+body {
+
+    @apply bg-bcg-body;
+}
+
+/* region Typo */
+h1, h2, h3, h4, h5, h6, p {
+    @apply font-normal break-words;
+}
+
+.content-wrapper, .page-header {
+    h1 {
+        @apply text-4xl text-heading mb-4 font-serif;
+    }
+
+    h2 {
+        @apply text-2xl text-heading mt-3;
+    }
+
+    h3 {
+        @apply text-xl text-heading mb-1 font-normal;
+    }
+
+    p {
+        @apply leading-[1.5] mb-6;
+    }
+}
+
+/* endregion Typo */
+
+/* region Header */
+.my-header-stripe {
+    height: 5px;
+    background: repeating-linear-gradient(
+        90deg,
+        #fde9e7 0px, #fde9e7 10%,
+        #fabcb7 10%, #fabcb7 20%,
+        #f46358 20%, #f46358 30%,
+        #f79088 30%, #f79088 40%,
+        #d71d0f 40%, #d71d0f 50%,
+        #f03728 50%, #f03728 60%,
+        #f79088 60%, #f79088 70%,
+        #f46358 70%, #f46358 80%,
+        #fabcb7 80%, #fabcb7 90%,
+        #fde9e7 90%, #fde9e7 100%
+    );
+}
+
+.my-header {
+    /*@apply bg-bcg-primary  border-bor-primary/10;*/
+    @apply w-full fixed top-0 left-0  z-[2100] h-20 bg-white;
+    @apply text-header ;
+    /*top: 5px !important;*/
+    box-shadow: 0 3px 10px 0 rgba(50, 50, 50, 0.2);
+
+
+}
+
+.my-header-logo {
+    svg {
+        @apply h-[1.8rem] w-auto lg:h-[2.2rem];
+    }
+}
+
+.my-header-slogan {
+    @apply text-sm text-header/80 italic;
+}
+
+.my-nav-list-item {
+    @apply uppercase pl-6;
+
+    &:hover {
+        @apply underline text-link-hover;
+    }
+
+    .text-base {
+        @apply text-brand-primary;
+    }
+}
+
+.my-header-delimiter {
+    @apply pt-20;
+}
+
+/*@media only screen and (min-width: 1200px) {
+    .my-header {
+        @apply h-28 px-8 py-4;
+    }
+}*/
+
+/* endregion Header */
+
+/* region Footer */
+.my-footer {
+    @apply text-footer text-sm mt-8  border-t border-bor-primary/10 dark:border-white/10;
+
+    a {
+        @apply no-underline font-normal text-sm text-footer hover:text-footer-muted hover:underline;
+    }
+}
+
+/* endregion Footer */
+
+/* region Jumbotron */
+.my-jumbotron {
+    @apply pt-6 mb-8;
+    /* >section {
+         @apply border-b border-black/10 ;
+     }*/
+}
+
+/* endregion Jumbotron */
+
+/* region Homepage */
+.my-home-hero {
+    @apply flex items-center justify-between;
+
+    > div {
+        @apply w-1/2;
+    }
+
+    > div:last-child {
+        @apply flex justify-end;
+    }
+
+    h1 {
+        @apply text-heading sm:text-[2.8rem] font-serif sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+    }
+
+    p {
+        @apply text-[1.4rem] my-8;
+    }
+
+    .home-hero-svg svg {
+        @apply h-[200px] md:h-[400px] w-full ;
+    }
+
+    /* h1{
+         @apply text-heading sm:text-[2.8rem] font-serif sm:tracking-[1rem] sm:[text-shadow:2px_2px_2px_rgba(150,150,150,0.8)];
+     }*/
+    /*> div{
+        @apply text-[1.4rem] my-8;
+    }*/
+
+}
+
+.my-home-feature {
+    @apply bg-base-100 w-96 shadow-sm;
+
+    .card-body {
+        @apply items-center text-center;
+
+        .my-icon {
+            svg {
+                @apply w-[46px] h-[46px];
+            }
+        }
+    }
+
+
+}
+
+.my-homepage-section {
+    @apply mt-8;
+
+    > h2 {
+        @apply text-2xl text-heading uppercase mb-4;
+    }
+}
+
+.pages-contact{
+    .content-container{
+        @apply flex flex-wrap gap-4;
+    }
+
+    .content-container > * {
+        flex: 1 1 0;
+        min-width: 0;
+    }
+}
diff --git a/resources/css/vades/theme.css b/resources/css/vades/theme.css
new file mode 100644
index 0000000..b30d785
--- /dev/null
+++ b/resources/css/vades/theme.css
@@ -0,0 +1,110 @@
+
+@theme {
+
+
+    --font-size-base: 1.2rem;
+    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+    'Segoe UI Symbol', 'Noto Color Emoji';
+    --color-brand-primary: #A21C13;
+    --color-brand-primary-muted: #f46358;
+    --color-brand-secondary: #F79A0F;
+    --color-brand-secondary-muted: #faca84;
+    --color-base: #444444;
+    --color-muted: #696969;
+    --color-inverted: #e9ebfc;
+    --color-accent: #000000;
+    --color-heading: #A21C13;
+    --color-h1: #A21C13;
+    --color-link: #A21C13;
+    --color-link-hover: #f46358;
+    --color-btn: #A21C13;
+    --color-btn-hover: #A3160B;
+    --color-input: #000000;
+    --color-input-focus: #A21C13;
+    --color-header: #A21C13;
+    --color-header-muted: #f46358;
+    --color-footer:  #A21C13;
+    --color-footer-muted: #f46358;
+    --color-supplementary: #091843;
+    --color-supplementary-muted: #0f2970;
+    --color-info: #1e429f;
+    --color-success: #03543f;
+    --color-warning: #723b13;
+    --color-danger: #9b1c1c;
+    --color-featured: #0a0a0a;
+    --color-blog: #0a0a0a;
+    --color-place: #0a0a0a;
+    --color-album: #0a0a0a;
+    --color-bor-base: #FEF9F2;
+    --color-bor-muted: #f4f6fd;
+    --color-bor-primary: #A3160B;
+    --color-bor-secondary:#F79A0F;
+    --color-bor-btn: #dbdbdb;
+    --color-bor-input: #dbdbdb;
+    --color-bor-input-focus: #A3160B;
+    --color-bor-footer:#fde9e7;
+    --color-bor-supplementary: #f4f6fd;
+    --color-bor-info: #a4cafe;
+    --color-bor-success: #84e1bc;
+    --color-bor-warning: #faca15;
+    --color-bor-danger: #f8b4b4;
+    --color-bcg-body: #FEF7EA;
+    --color-bcg-base: #F7F8F2;
+    --color-bcg-primary:#A3160B;
+    --color-bcg-primary-muted: #bf5e5e;
+    --color-bcg-secondary:#F69A13;
+    --color-bcg-secondary-muted:#faca84;
+    --color-bcg-btn: #F7F8F2;
+    --color-bcg-btn-hover: #FFFFFF;
+    --color-bcg-input: #fef4e6;
+    --color-bcg-info: #ebf5ff;
+    --color-bcg-success: #f3faf7;
+    --color-bcg-warning: #fdfdea;
+    --color-bcg-danger: #fdf2f2;
+    --color-bcg-accent: #e9eefc;
+    --color-bcg-header: #FFFFFF;
+    --color-bcg-jumbotron: #f4e9d4;
+    --color-bcg-supplementary: #F7F8F2;
+    --color-bcg-footer: #FFFFFF;
+    --color-bcg-card: #F7F8F2;
+    --color-bcg-featured: #F7F8F2;
+    --color-bcg-article: #fef4e6;
+    --color-bcg-blog: #F7F8F2;
+    --color-bcg-place: #F7F8F2;
+    --color-bcg-album: #F7F8F2;
+    --color-bcg-form: #f4e9d4;
+    --color-icon-primary: #A21C13;
+    --color-icon-secondary: #F79A0F;
+
+    /* * SHEAF UI color scheme */
+   /* --color-primary: var(--color-red-500);
+    --color-primary-content: var(--color-blue-700);
+    --color-primary-fg: var(--color-blue-600);*/
+
+    --radius-field: 0.25rem;
+    --radius-box: 0.5rem;
+}
+
+
+/*
+@layer theme {
+    .dark {
+        --color-primary: var(--color-white);
+        --color-primary-content: var(--color-white);
+        --color-primary-fg:  var(--color-neutral-800);
+    }
+}*/
+
+/*@custom-variant dark (&:where(.dark, .dark *));*/
+/* DaisyUI */
+/*:root {
+    !* v5 Variable Name *!
+    --color-primary: #A21C13;
+    --color-secondary: #F79A0F;
+}*/
+/*:root {
+    color-scheme: light !important;
+}*/
+/* Changes dark strategy to look for a .dark class instead of system settings */
+@variant dark (&:where(.dark, .dark *));
+
diff --git a/resources/css/vades/utility.css b/resources/css/vades/utility.css
new file mode 100644
index 0000000..42e032a
--- /dev/null
+++ b/resources/css/vades/utility.css
@@ -0,0 +1,7 @@
+
+@utility container {
+    margin-inline: auto;
+    width: 100%;
+    max-width: 1200px;
+    padding-inline: 2rem;
+}
diff --git a/resources/js/app.js b/resources/js/app.js
index e69de29..8b13789 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -0,0 +1 @@
+
diff --git a/resources/views/components/_shared/alert.blade.php b/resources/views/components/_shared/alert.blade.php
new file mode 100644
index 0000000..06bf6ed
--- /dev/null
+++ b/resources/views/components/_shared/alert.blade.php
@@ -0,0 +1,3 @@
+
class(['alert'])}} role="alert"> +
{{ $slot }}
+
\ No newline at end of file diff --git a/resources/views/components/_shared/badge.blade.php b/resources/views/components/_shared/badge.blade.php new file mode 100644 index 0000000..590fbcc --- /dev/null +++ b/resources/views/components/_shared/badge.blade.php @@ -0,0 +1,9 @@ +class(['badge'])}}> + + {{ $slot }} + + + @isset($notify) + attributes->class(['badge-notify'])}}>{{$notify}} + @endisset + \ No newline at end of file diff --git a/resources/views/components/_shared/card.blade.php b/resources/views/components/_shared/card.blade.php new file mode 100644 index 0000000..4e8df4f --- /dev/null +++ b/resources/views/components/_shared/card.blade.php @@ -0,0 +1,13 @@ +
class(['card'])}}> + @isset($header) +
attributes->class(['card-header'])}}>{{$header}}
+ @endisset + + @isset($body) +
attributes->class(['card-body'])}}>{{$body}}
+ @endisset + + @isset($footer) +
attributes->class(['card-footer'])}}>{{$footer}}
+ @endisset +
\ No newline at end of file diff --git a/resources/views/components/_shared/categories-dropdown.blade.php b/resources/views/components/_shared/categories-dropdown.blade.php new file mode 100644 index 0000000..9862a46 --- /dev/null +++ b/resources/views/components/_shared/categories-dropdown.blade.php @@ -0,0 +1,39 @@ + +@props(['type' => 'place','route' => 'placeList','label' => 'placeCategoryList']) +@if(isset($categories) && count($categories) > 0) + + + + {{ __($label) }} + + + + + + + + + + +@endif \ No newline at end of file diff --git a/resources/views/components/_shared/dropdown.blade.php b/resources/views/components/_shared/dropdown.blade.php new file mode 100644 index 0000000..82f9227 --- /dev/null +++ b/resources/views/components/_shared/dropdown.blade.php @@ -0,0 +1,22 @@ +
class(['relative'])}}> + @isset($header) +
attributes->class([])}} + @click="open = !open" + @click.away="open = false" + style="cursor:pointer;"> + {{$header}} +
+ @endisset + + @isset($body) + +
attributes->class([''])}} + x-show="open" + x-transition + style="display: none;"> + + {{$body}} + +
+ @endisset +
\ No newline at end of file diff --git a/resources/views/components/_shared/gtag.blade.php b/resources/views/components/_shared/gtag.blade.php new file mode 100644 index 0000000..87b6976 --- /dev/null +++ b/resources/views/components/_shared/gtag.blade.php @@ -0,0 +1,10 @@ +@if(config('myapp.gatMeasurementId')) + + +@endif \ No newline at end of file diff --git a/resources/views/components/_shared/iframe.blade.php b/resources/views/components/_shared/iframe.blade.php new file mode 100644 index 0000000..e281fcb --- /dev/null +++ b/resources/views/components/_shared/iframe.blade.php @@ -0,0 +1,9 @@ +@props(['src']) +
class(['iframe'])}}> + +
\ No newline at end of file diff --git a/resources/views/components/_shared/img-svg.blade.php b/resources/views/components/_shared/img-svg.blade.php new file mode 100644 index 0000000..1ef5a57 --- /dev/null +++ b/resources/views/components/_shared/img-svg.blade.php @@ -0,0 +1,10 @@ +@php + + $img = $img ?? ''; + $fullPath = storage_path('app/public/images/svg/' . $img . '.svg'); +@endphp + +@if(file_exists($fullPath)) + {!! file_get_contents($fullPath) !!} + @endif + \ No newline at end of file diff --git a/resources/views/components/_shared/jumbotron.blade.php b/resources/views/components/_shared/jumbotron.blade.php new file mode 100644 index 0000000..eff1679 --- /dev/null +++ b/resources/views/components/_shared/jumbotron.blade.php @@ -0,0 +1,6 @@ + +
class(['jumbotron'])}}> +
+ {{$slot}} +
+
\ No newline at end of file diff --git a/resources/views/components/_shared/lightbox.blade.php b/resources/views/components/_shared/lightbox.blade.php new file mode 100644 index 0000000..015bc8f --- /dev/null +++ b/resources/views/components/_shared/lightbox.blade.php @@ -0,0 +1,99 @@ +@props(['images']) +@props(['title']) +@isset($images) +
+
+ + +
+ @foreach($images as $index => $item) + + + {{ $item->title}} + + + @endforeach +
+ + +
+
+@endisset \ No newline at end of file diff --git a/resources/views/components/_shared/modal.blade.php b/resources/views/components/_shared/modal.blade.php new file mode 100644 index 0000000..2d38451 --- /dev/null +++ b/resources/views/components/_shared/modal.blade.php @@ -0,0 +1,46 @@ + + + + \ No newline at end of file diff --git a/resources/views/components/_shared/page-header.blade.php b/resources/views/components/_shared/page-header.blade.php new file mode 100644 index 0000000..33cc232 --- /dev/null +++ b/resources/views/components/_shared/page-header.blade.php @@ -0,0 +1,22 @@ +
class(['page-header'])}}> + @if(isset($image)) +
attributes->class([])}}>{{$image}}
+ @endif +
+ @if(!empty($title)) +

attributes->class(['page-header-title'])}}> + {{ $title }} +

+ @endif + @if(!empty($subtitle)) +

attributes->class(['pag-header-subtitle'])}}>{{ $subtitle }}

+ @endif + @if(!empty($description)) +
attributes->class(['page-header-description'])}}>{{ $description }}
+ @endif + @if(!empty($info)) +
attributes->class(['page-header-info'])}}>{{ $info }}
+ @endif +
+ +
\ No newline at end of file diff --git a/resources/views/components/_shared/pagination.blade.php b/resources/views/components/_shared/pagination.blade.php new file mode 100644 index 0000000..afade91 --- /dev/null +++ b/resources/views/components/_shared/pagination.blade.php @@ -0,0 +1,35 @@ + diff --git a/resources/views/components/_shared/panel.blade.php b/resources/views/components/_shared/panel.blade.php new file mode 100644 index 0000000..c311921 --- /dev/null +++ b/resources/views/components/_shared/panel.blade.php @@ -0,0 +1,9 @@ +
class(['panel'])}}> + @isset($header) +
attributes->class(['panel-heade'])}}>{{$header}}
+ @endisset + + @isset($body) +
attributes->class(['panel-body'])}}>{{$body}}
+ @endisset +
\ No newline at end of file diff --git a/resources/views/components/_shared/post-image.blade.php b/resources/views/components/_shared/post-image.blade.php new file mode 100644 index 0000000..f848c69 --- /dev/null +++ b/resources/views/components/_shared/post-image.blade.php @@ -0,0 +1,18 @@ +@props(['postImage']) +
class([])}}> + @if(!empty($postImage->title)) +

+ {{ $postImage->title }} +

+ @endif + {{ $postImage->title }} + @if(!empty($postImage->description)) +
+ {{ $postImage->description }} +
+ @endif +
\ No newline at end of file diff --git a/resources/views/components/_shared/prev-next.blade.php b/resources/views/components/_shared/prev-next.blade.php new file mode 100644 index 0000000..03f3551 --- /dev/null +++ b/resources/views/components/_shared/prev-next.blade.php @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/resources/views/components/default/article/index.blade.php b/resources/views/components/default/article/index.blade.php new file mode 100644 index 0000000..930ad02 --- /dev/null +++ b/resources/views/components/default/article/index.blade.php @@ -0,0 +1,28 @@ +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + +
+ @foreach($contents as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.article')) + + + + + @endforeach +
+
+ {!! $contents->links() !!} + + {{-- --}} +
+
diff --git a/resources/views/components/default/article/show-default.blade.php b/resources/views/components/default/article/show-default.blade.php new file mode 100644 index 0000000..add2d80 --- /dev/null +++ b/resources/views/components/default/article/show-default.blade.php @@ -0,0 +1,36 @@ + + + + + + + + + @if(!empty($page->featured_image_url)) +
+ {{ $page->title }} + {{--
The Alps in early winter.
--}} +
+ @endif +
+ {!! $page->rendered_content !!} +
+ @if($page->tags->isNotEmpty()) +

{{__('app.nav.tags')}}

+
+ + @foreach($page->tags as $tag) + + {{ $tag->name }} + + @endforeach +
+ @endif +
+ + +
+
diff --git a/resources/views/components/default/home/articles.blade.php b/resources/views/components/default/home/articles.blade.php new file mode 100644 index 0000000..2c1af60 --- /dev/null +++ b/resources/views/components/default/home/articles.blade.php @@ -0,0 +1,16 @@ +@inject('carbon', 'Carbon\Carbon') + +

{{__('app.nav.recentPosts')}}

+ +
+ @foreach($articles as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.article')) + + + + @endforeach +
+ diff --git a/resources/views/components/default/home/features.blade.php b/resources/views/components/default/home/features.blade.php new file mode 100644 index 0000000..2bdfab7 --- /dev/null +++ b/resources/views/components/default/home/features.blade.php @@ -0,0 +1,100 @@ + +
+
+ +
+ +

This is feature 1

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 2

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 3

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 4

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 5

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 6

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+ +
diff --git a/resources/views/components/default/home/hero.blade.php b/resources/views/components/default/home/hero.blade.php new file mode 100644 index 0000000..41afc34 --- /dev/null +++ b/resources/views/components/default/home/hero.blade.php @@ -0,0 +1,6 @@ +
class(['text-center my-10'])}}> +

{{$page->title}}

+
{{$page->excerpt}}
+ + +
diff --git a/resources/views/components/default/home/index.blade.php b/resources/views/components/default/home/index.blade.php new file mode 100644 index 0000000..1a7f87e --- /dev/null +++ b/resources/views/components/default/home/index.blade.php @@ -0,0 +1,32 @@ + + @if(!empty($page)) + + @endif + + + @if(count($placesFeatured) > 0) +
+ +
+ @endif + + @if(count($places) > 0) +
+ +
+ @endif + @if(count($articles) > 0) +
+ +
+ @endif + + @if(count($images) > 0) +
+ +
+ @endif + +
diff --git a/resources/views/components/default/home/photo-gallery.blade.php b/resources/views/components/default/home/photo-gallery.blade.php new file mode 100644 index 0000000..de4a293 --- /dev/null +++ b/resources/views/components/default/home/photo-gallery.blade.php @@ -0,0 +1,17 @@ +

{{__('app.nav.photoGallery')}}

+ + + + diff --git a/resources/views/components/default/home/places-featured.blade.php b/resources/views/components/default/home/places-featured.blade.php new file mode 100644 index 0000000..ba24428 --- /dev/null +++ b/resources/views/components/default/home/places-featured.blade.php @@ -0,0 +1,13 @@ + +

{{__('app.nav.bestPlacesToVisitIn',['name'=>'Nuremberg'])}}

+ diff --git a/resources/views/components/default/home/places.blade.php b/resources/views/components/default/home/places.blade.php new file mode 100644 index 0000000..2d28b83 --- /dev/null +++ b/resources/views/components/default/home/places.blade.php @@ -0,0 +1,15 @@ + +

{{__('app.nav.otherPlaces')}}

+
+ @foreach($places as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.place')) + @php($item->excerpt = null) + + + + @endforeach +
+ diff --git a/resources/views/components/default/layout.blade.php b/resources/views/components/default/layout.blade.php new file mode 100644 index 0000000..1ffbfd6 --- /dev/null +++ b/resources/views/components/default/layout.blade.php @@ -0,0 +1,46 @@ + + + + + + + + {{ filled($title ?? null) ? $title : config('myapp.metaTitle') }} + + + + + + + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite([$globalCssPath, 'resources/js/app.js']) + @else + @vite([$globalCssPath, 'resources/js/app.js']) + @endif + @livewireStyles + {{-- --}} + + +
+ {{-- #region Header --}} + +
+ {{-- #endregion Header --}} + @if(isset($jumbotron) && !empty($jumbotron)) + {{ $jumbotron }} + @endif +
+ {{ $slot }} +
+ + {{-- --}} + + +
+@livewireScripts + + diff --git a/resources/views/components/default/page/index.blade.php b/resources/views/components/default/page/index.blade.php new file mode 100644 index 0000000..bdaff47 --- /dev/null +++ b/resources/views/components/default/page/index.blade.php @@ -0,0 +1,31 @@ + +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + + + @if(!empty($page->featured_image_url)) +
+ {{ $page->title }} +
The Alps in early winter.
+
+ @endif +
+
+ {!! $page->renderedContent !!} +
+ @if(!empty($page->livewireWidget) && $page->livewireWidget === 'contact-form') + + @endif +
+
diff --git a/resources/views/components/default/partials/footer/brand.blade.php b/resources/views/components/default/partials/footer/brand.blade.php new file mode 100644 index 0000000..cafe899 --- /dev/null +++ b/resources/views/components/default/partials/footer/brand.blade.php @@ -0,0 +1,3 @@ +
class(['my-footer-brand'])}}> + © {{ date('Y') }} {{ config('myapp.name') }} +
\ No newline at end of file diff --git a/resources/views/components/default/partials/footer/index.blade.php b/resources/views/components/default/partials/footer/index.blade.php new file mode 100644 index 0000000..3acd476 --- /dev/null +++ b/resources/views/components/default/partials/footer/index.blade.php @@ -0,0 +1,6 @@ +
+ +
\ No newline at end of file diff --git a/resources/views/components/default/partials/footer/nav.blade.php b/resources/views/components/default/partials/footer/nav.blade.php new file mode 100644 index 0000000..4dbfb65 --- /dev/null +++ b/resources/views/components/default/partials/footer/nav.blade.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/resources/views/components/default/partials/header/brand.blade.php b/resources/views/components/default/partials/header/brand.blade.php new file mode 100644 index 0000000..02e0f18 --- /dev/null +++ b/resources/views/components/default/partials/header/brand.blade.php @@ -0,0 +1,7 @@ +
class(['my-header-brand'])}}> + + {{ config('myapp.slogan') }} +
\ No newline at end of file diff --git a/resources/views/components/default/partials/header/index.blade.php b/resources/views/components/default/partials/header/index.blade.php new file mode 100644 index 0000000..23b7986 --- /dev/null +++ b/resources/views/components/default/partials/header/index.blade.php @@ -0,0 +1,35 @@ +
+
+
+ + @if(!empty(config('myapp.headerWidgets.searchInContentType'))) + + + @endif + + @if(!empty(config('myapp.headerWidgets.articleCategories'))) + + + + + @endif + + @if(!empty(config('myapp.headerWidgets.placeCategories'))) + + + + + @endif +
+ +
+
+
diff --git a/resources/views/components/default/partials/header/nav.blade.php b/resources/views/components/default/partials/header/nav.blade.php new file mode 100644 index 0000000..3fde030 --- /dev/null +++ b/resources/views/components/default/partials/header/nav.blade.php @@ -0,0 +1,33 @@ +
+ +
+ + + +
+
+ + +
+
diff --git a/resources/views/components/default/partials/page-header.blade.php b/resources/views/components/default/partials/page-header.blade.php new file mode 100644 index 0000000..f55f4e0 --- /dev/null +++ b/resources/views/components/default/partials/page-header.blade.php @@ -0,0 +1,26 @@ +@inject('carbon', 'Carbon\Carbon') + + @if(empty($page->featured_image_url) && !empty($page->cover_image_url)) + + {{ $page->title }} + + + @endif + + + {{ $page->title }} + + + {{ $page->subtitle ?? null }} + + + {{ $page->excerpt ?? null }} + + @if(isset($page->user->name)) + + {{$page->user->name}} + {{ $carbon::parse($page->createdAt)->format('Y-m-d') }} + + @endif + + \ No newline at end of file diff --git a/resources/views/components/default/partials/supplementary/index.blade.php b/resources/views/components/default/partials/supplementary/index.blade.php new file mode 100644 index 0000000..bb19f3d --- /dev/null +++ b/resources/views/components/default/partials/supplementary/index.blade.php @@ -0,0 +1,7 @@ +
+
+ + supplementary +
+ +
\ No newline at end of file diff --git a/resources/views/components/default/photo-gallery/index.blade.php b/resources/views/components/default/photo-gallery/index.blade.php new file mode 100644 index 0000000..7421d6f --- /dev/null +++ b/resources/views/components/default/photo-gallery/index.blade.php @@ -0,0 +1,24 @@ +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + + + \ No newline at end of file diff --git a/resources/views/components/default/photo-gallery/show.blade.php b/resources/views/components/default/photo-gallery/show.blade.php new file mode 100644 index 0000000..26066dd --- /dev/null +++ b/resources/views/components/default/photo-gallery/show.blade.php @@ -0,0 +1,19 @@ + +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + + + @if(!empty($images)) + + @endif + \ No newline at end of file diff --git a/resources/views/components/default/place/index.blade.php b/resources/views/components/default/place/index.blade.php new file mode 100644 index 0000000..5ef1e41 --- /dev/null +++ b/resources/views/components/default/place/index.blade.php @@ -0,0 +1,28 @@ +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + +
+ @foreach($contents as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.place')) + + + + + @endforeach +
+
+ {!! $contents->links() !!} + + {{-- --}} +
+
diff --git a/resources/views/components/default/place/show-header.blade.php b/resources/views/components/default/place/show-header.blade.php new file mode 100644 index 0000000..97c4d65 --- /dev/null +++ b/resources/views/components/default/place/show-header.blade.php @@ -0,0 +1,37 @@ + +
class(['sm:flex justify-between gap-8 flex-wrap'])}}> + @php($coverImage = !empty($page->cover_image_url) ? $page->cover_image_url : config('myapp.image.placeholder.place')) + +
+
+ {{  $page->title }} +
+
+ + + @if(isset($page->googleMapEmbedUrl)) +
+ +
+ @endif + +
\ No newline at end of file diff --git a/resources/views/components/default/place/show-highlights.blade.php b/resources/views/components/default/place/show-highlights.blade.php new file mode 100644 index 0000000..81e2a91 --- /dev/null +++ b/resources/views/components/default/place/show-highlights.blade.php @@ -0,0 +1,8 @@ +@props(['highlights']) +
+ @foreach($highlights as $item) + @php($item->created_at = null) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.place')) + + @endforeach +
diff --git a/resources/views/components/default/place/show-related.blade.php b/resources/views/components/default/place/show-related.blade.php new file mode 100644 index 0000000..e632be4 --- /dev/null +++ b/resources/views/components/default/place/show-related.blade.php @@ -0,0 +1,11 @@ +@props(['related']) + + diff --git a/resources/views/components/default/place/show.blade.php b/resources/views/components/default/place/show.blade.php new file mode 100644 index 0000000..0442164 --- /dev/null +++ b/resources/views/components/default/place/show.blade.php @@ -0,0 +1,37 @@ +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + + + @if(!empty($images)) + + @endif + @if(!empty($page->rendered_content)) +
+ {!! $page->rendered_content !!} +
+ @endif + + @if(count($highlights) > 0) +

{{ $page->title }} {{__('app.label.highlights') }}

+ + @endif + + @if(count($related) > 0) +

{{__('app.label.relatedPlaces')}}

+ + @endif +
+ + +
+
diff --git a/resources/views/components/default/tag/index.blade.php b/resources/views/components/default/tag/index.blade.php new file mode 100644 index 0000000..101dbf8 --- /dev/null +++ b/resources/views/components/default/tag/index.blade.php @@ -0,0 +1,27 @@ +@php + if(isset($page->user)){ + $page->user = null; + } +@endphp + + + + + + + +
+
+ @foreach($tags as $tag) + + {{ $tag->name }} {{-- ({{ $tag->contents_count }}) --}} + + + @endforeach +
+
+ +
diff --git a/resources/views/components/ivnbg/home/hero.blade.php b/resources/views/components/ivnbg/home/hero.blade.php new file mode 100644 index 0000000..324b42d --- /dev/null +++ b/resources/views/components/ivnbg/home/hero.blade.php @@ -0,0 +1,6 @@ +
class(['text-center my-10'])}}> +

{{$page->title}}

+
{{$page->excerpt}}
+ + +
diff --git a/resources/views/components/ivnbg/home/index.blade.php b/resources/views/components/ivnbg/home/index.blade.php new file mode 100644 index 0000000..ca69f18 --- /dev/null +++ b/resources/views/components/ivnbg/home/index.blade.php @@ -0,0 +1,30 @@ + + @if(!empty($page)) + + @endif + @if(count($placesFeatured) > 0) +
+ +
+ @endif + + @if(count($places) > 0) +
+ +
+ @endif + @if(count($articles) > 0) +
+ +
+ @endif + + @if(count($images) > 0) +
+ +
+ @endif + +
diff --git a/resources/views/components/martinvach/home/articles.blade.php b/resources/views/components/martinvach/home/articles.blade.php new file mode 100644 index 0000000..5a40ae7 --- /dev/null +++ b/resources/views/components/martinvach/home/articles.blade.php @@ -0,0 +1,15 @@ +@inject('carbon', 'Carbon\Carbon') + +

{{__('app.nav.recentPosts')}}

+
+ @foreach($articles as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.article')) + + + + @endforeach +
+ \ No newline at end of file diff --git a/resources/views/components/martinvach/home/features.blade.php b/resources/views/components/martinvach/home/features.blade.php new file mode 100644 index 0000000..2bdfab7 --- /dev/null +++ b/resources/views/components/martinvach/home/features.blade.php @@ -0,0 +1,100 @@ + +
+
+ +
+ +

This is feature 1

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 2

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 3

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 4

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 5

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+
+ +
+ +

This is feature 6

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ + HashTag + HashTag + HashTag +
+ + +
+
+ +
diff --git a/resources/views/components/martinvach/home/hero.blade.php b/resources/views/components/martinvach/home/hero.blade.php new file mode 100644 index 0000000..cf5338f --- /dev/null +++ b/resources/views/components/martinvach/home/hero.blade.php @@ -0,0 +1,6 @@ +
class(['text-center'])}}> +

{{$page->title}}

+
{{$page->excerpt}}
+ + +
\ No newline at end of file diff --git a/resources/views/components/martinvach/home/index.blade.php b/resources/views/components/martinvach/home/index.blade.php new file mode 100644 index 0000000..9cf6a93 --- /dev/null +++ b/resources/views/components/martinvach/home/index.blade.php @@ -0,0 +1,30 @@ + + @if(!empty($page)) + + @endif + @if(count($placesFeatured) > 0) +
+ +
+ @endif + + @if(count($places) > 0) +
+ +
+ @endif + @if(count($articles) > 0) +
+ +
+ @endif + + @if(count($images) > 0) +
+ +
+ @endif + +
diff --git a/resources/views/components/martinvach/home/photo-gallery.blade.php b/resources/views/components/martinvach/home/photo-gallery.blade.php new file mode 100644 index 0000000..b660295 --- /dev/null +++ b/resources/views/components/martinvach/home/photo-gallery.blade.php @@ -0,0 +1,17 @@ +

{{__('app.nav.photoGallery')}}

+ + + + \ No newline at end of file diff --git a/resources/views/components/martinvach/home/places-featured.blade.php b/resources/views/components/martinvach/home/places-featured.blade.php new file mode 100644 index 0000000..0fd6a3b --- /dev/null +++ b/resources/views/components/martinvach/home/places-featured.blade.php @@ -0,0 +1,23 @@ + +

{{__('app.nav.bestPlacesToVisitIn',['name'=>'Nuremberg'])}}

+
+ @foreach($places as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.place')) + + + + {{ $item->title}} + + + +

+ {{ $item->title }}

+ + +
+
+
+ @endforeach +
\ No newline at end of file diff --git a/resources/views/components/martinvach/home/places.blade.php b/resources/views/components/martinvach/home/places.blade.php new file mode 100644 index 0000000..93c9a5a --- /dev/null +++ b/resources/views/components/martinvach/home/places.blade.php @@ -0,0 +1,29 @@ + +

{{__('app.nav.otherPlaces')}}

+
+ @foreach($places as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.place')) + + + + {{ $item->title}} + + + +

+ {{ $item->title }}

+
+ + {{__('app.nav.readMore')}} + +
+ + @endforeach +
+ diff --git a/resources/views/components/myprompties/home.blade.php b/resources/views/components/myprompties/home.blade.php new file mode 100644 index 0000000..3e837c4 --- /dev/null +++ b/resources/views/components/myprompties/home.blade.php @@ -0,0 +1,34 @@ + + + + + + Martin Vach Photography | Coming Soon + + + + + + + +
+
+

+ MyPrompties.com +

+
+
+ + +
+ content here +
+ + + diff --git a/resources/views/components/ui/my-card/article.blade.php b/resources/views/components/ui/my-card/article.blade.php new file mode 100644 index 0000000..9e585b3 --- /dev/null +++ b/resources/views/components/ui/my-card/article.blade.php @@ -0,0 +1,20 @@ +@inject('carbon', 'Carbon\Carbon') + + + {{ $item->title}} + + + +
{{ $carbon::parse($item->created_at)->format('Y-m-d') }}
+ +
+ {{ $item->excerpt }} +
+
+ + {{__('app.nav.readMore')}} + +
diff --git a/resources/views/components/ui/my-card/featured.blade.php b/resources/views/components/ui/my-card/featured.blade.php new file mode 100644 index 0000000..d64cb7f --- /dev/null +++ b/resources/views/components/ui/my-card/featured.blade.php @@ -0,0 +1,9 @@ + diff --git a/resources/views/components/ui/my-card/gallery.blade.php b/resources/views/components/ui/my-card/gallery.blade.php new file mode 100644 index 0000000..5beff9d --- /dev/null +++ b/resources/views/components/ui/my-card/gallery.blade.php @@ -0,0 +1,9 @@ +
class(['my-card-gallery'])}}> +
+ {{ $alt ?? 'image'}} + + @if(!empty($title)) +
{{ $title}}
+ @endif +
+
diff --git a/resources/views/components/ui/my-card/index.blade.php b/resources/views/components/ui/my-card/index.blade.php new file mode 100644 index 0000000..935a3d4 --- /dev/null +++ b/resources/views/components/ui/my-card/index.blade.php @@ -0,0 +1,13 @@ +
class(['my-card'])}}> + @isset($header) +
attributes->class(['my-card-header'])}}>{{$header}}
+ @endisset + + @isset($body) +
attributes->class(['my-card-body'])}}>{{$body}}
+ @endisset + + @isset($footer) +
attributes->class(['my-card-footer'])}}>{{$footer}}
+ @endisset +
diff --git a/resources/views/components/ui/my-card/place.blade.php b/resources/views/components/ui/my-card/place.blade.php new file mode 100644 index 0000000..9c13db5 --- /dev/null +++ b/resources/views/components/ui/my-card/place.blade.php @@ -0,0 +1,21 @@ + + + {{ $item->title}} + + + + @if (filled($item->subtitle)) +
{{ $item->subtitle }}
+ @endif + +
+ {{ $item->excerpt }} +
+
+ + {{__('app.nav.readMore')}} + +
diff --git a/resources/views/components/ui/my-categories-dropdown/index.blade.php b/resources/views/components/ui/my-categories-dropdown/index.blade.php new file mode 100644 index 0000000..d9b266a --- /dev/null +++ b/resources/views/components/ui/my-categories-dropdown/index.blade.php @@ -0,0 +1,21 @@ +@php +$composerCategories = collect(data_get($composerCategory ?? [], $categoryType, [])); +@endphp + +@if($composerCategories->count() > 1) +
+ + + +
+@endif diff --git a/resources/views/components/ui/my-dropdown/index.blade.php b/resources/views/components/ui/my-dropdown/index.blade.php new file mode 100644 index 0000000..b7288ea --- /dev/null +++ b/resources/views/components/ui/my-dropdown/index.blade.php @@ -0,0 +1,22 @@ +
class(['relative'])}}> + @isset($header) +
attributes->class([])}} + @click="open = !open" + @click.away="open = false" + style="cursor:pointer;"> + {{$header}} +
+ @endisset + + @isset($body) + +
attributes->class(['absolute left-0 top-full w-64 bg-white dark:bg-neutral-900 border border-black/10 dark:border-white/10 shadow-lg rounded z-50 max-h-128 overflow-auto divide-y divide-gray-100'])}} + x-show="open" + x-transition + style="display: none;"> + + {{$body}} + +
+ @endisset +
\ No newline at end of file diff --git a/resources/views/components/ui/my-gtag/index.blade.php b/resources/views/components/ui/my-gtag/index.blade.php new file mode 100644 index 0000000..87b6976 --- /dev/null +++ b/resources/views/components/ui/my-gtag/index.blade.php @@ -0,0 +1,10 @@ +@if(config('myapp.gatMeasurementId')) + + +@endif \ No newline at end of file diff --git a/resources/views/components/ui/my-iframe/index.blade.php b/resources/views/components/ui/my-iframe/index.blade.php new file mode 100644 index 0000000..14bcb78 --- /dev/null +++ b/resources/views/components/ui/my-iframe/index.blade.php @@ -0,0 +1,10 @@ +@props(['src']) +
class(['my-iframe-wrapper'])}}> + +
diff --git a/resources/views/components/ui/my-img-svg/index.blade.php b/resources/views/components/ui/my-img-svg/index.blade.php new file mode 100644 index 0000000..7919b1e --- /dev/null +++ b/resources/views/components/ui/my-img-svg/index.blade.php @@ -0,0 +1,10 @@ +@php + + $img = $img ?? ''; + $fullPath = storage_path(config('myapp.image.svgPath').'/' . $img . '.svg'); +@endphp + +@if(file_exists($fullPath)) + {!! file_get_contents($fullPath) !!} + @endif + \ No newline at end of file diff --git a/resources/views/components/ui/my-jumbotron/index.blade.php b/resources/views/components/ui/my-jumbotron/index.blade.php new file mode 100644 index 0000000..87db885 --- /dev/null +++ b/resources/views/components/ui/my-jumbotron/index.blade.php @@ -0,0 +1,6 @@ + +
class(['my-jumbotron'])}}> +
+ {{$slot}} +
+
diff --git a/resources/views/components/ui/my-lightbox/index.blade.php b/resources/views/components/ui/my-lightbox/index.blade.php new file mode 100644 index 0000000..55291ba --- /dev/null +++ b/resources/views/components/ui/my-lightbox/index.blade.php @@ -0,0 +1,95 @@ +@props(['images']) +@props(['title']) +@isset($images) +
+
+ + +
+ @foreach($images as $index => $item) + + @endforeach +
+ + +
+
+@endisset diff --git a/resources/views/components/ui/my-list/index.blade.php b/resources/views/components/ui/my-list/index.blade.php new file mode 100644 index 0000000..e7a75c3 --- /dev/null +++ b/resources/views/components/ui/my-list/index.blade.php @@ -0,0 +1,22 @@ +@inject('carbon', 'Carbon\Carbon') + +
class(['list-row'])}}> +
+ {{ $item->title}} +
+
+ + @if (filled($item->subtitle)) +
{{ $item->subtitle }}
+ @endif + @if (filled($item->created_at)) +
{{ $carbon::parse($item->created_at)->format('Y-m-d') }}
+ @endif + @if (filled($item->excerpt )) +
{{ $item->excerpt }}
+ @endif +
+ + + +
diff --git a/resources/views/components/ui/my-modal/index.blade.php b/resources/views/components/ui/my-modal/index.blade.php new file mode 100644 index 0000000..f54962e --- /dev/null +++ b/resources/views/components/ui/my-modal/index.blade.php @@ -0,0 +1,46 @@ + + + + diff --git a/resources/views/components/ui/my-page-header/index.blade.php b/resources/views/components/ui/my-page-header/index.blade.php new file mode 100644 index 0000000..fac34ec --- /dev/null +++ b/resources/views/components/ui/my-page-header/index.blade.php @@ -0,0 +1,21 @@ +
class(['my-page-header'])}}> + @if(isset($image)) +
attributes->class([])}}>{{$image}}
+ @endif +
+ @if(!empty($title)) +

attributes->class(['my-page-header-title'])}}> {{ $title }}

+ + @endif + @if(!empty($subtitle)) +

attributes->class(['my-page-header-subtitle'])}}>{{ $subtitle }}

+ @endif + @if(!empty($description)) +
attributes->class(['my-page-header-description'])}}>{{ $description }}
+ @endif + @if(!empty($info)) +
attributes->class(['my-page-header-info'])}}>{{ $info }}
+ @endif +
+ +
diff --git a/resources/views/components/ui/my-panel/index.blade.php b/resources/views/components/ui/my-panel/index.blade.php new file mode 100644 index 0000000..cb51fa4 --- /dev/null +++ b/resources/views/components/ui/my-panel/index.blade.php @@ -0,0 +1,9 @@ +
class(['my-panel'])}}> + @isset($header) +
attributes->class(['my-panel-header'])}}>{{$header}}
+ @endisset + + @isset($body) +
attributes->class(['my-panel-body'])}}>{{$body}}
+ @endisset +
diff --git a/resources/views/components/ui/my-prev-next/index.blade.php b/resources/views/components/ui/my-prev-next/index.blade.php new file mode 100644 index 0000000..aae0849 --- /dev/null +++ b/resources/views/components/ui/my-prev-next/index.blade.php @@ -0,0 +1,14 @@ + + diff --git a/resources/views/components/vades/home/articles.blade.php b/resources/views/components/vades/home/articles.blade.php new file mode 100644 index 0000000..937434f --- /dev/null +++ b/resources/views/components/vades/home/articles.blade.php @@ -0,0 +1,15 @@ +@inject('carbon', 'Carbon\Carbon') + +

{{__('app.nav.recentPosts')}}

+
+ @foreach($articles as $item) + @php($coverImage = !empty($item->cover_image_url) ? $item->cover_image_url : config('myapp.image.placeholder.article')) + + + + @endforeach +
+ diff --git a/resources/views/components/vades/home/contact.blade.php b/resources/views/components/vades/home/contact.blade.php new file mode 100644 index 0000000..d6142ad --- /dev/null +++ b/resources/views/components/vades/home/contact.blade.php @@ -0,0 +1,16 @@ + +
+
+

+ Not sure where to start? +

+

+ +That's fine. Drop a few lines about your project or challenge and I will take it from there. +

+
+ +
+ +
+
\ No newline at end of file diff --git a/resources/views/components/vades/home/features.blade.php b/resources/views/components/vades/home/features.blade.php new file mode 100644 index 0000000..81187ca --- /dev/null +++ b/resources/views/components/vades/home/features.blade.php @@ -0,0 +1,92 @@ + +
+
+ +
+ +

Architecture Built to Last

+

Systems designed around your actual growth — not yesterday's assumptions or tomorrow's rewrites.

+
+ + EnterpriseEngineering + TechDebt + SystemDesign +
+ + +
+
+
+
+ +

Full-Stack, Zero Gaps

+

+ I ship the whole thing. Frontend, backend, infrastructure — no external handoffs, no broken context. +

+
+ FullStackDev + TypeScript + PHP + +
+
+
+
+
+ +

AI That Ships Faster

+

+ Embedded AI tooling that removes friction from the build cycle — without compromising what goes out the door. + +

+
+ GenerativeAI + EngineeringEfficiency + LLM +
+
+
+
+
+ +

Databases That Hold Up

+

+ Performant schemas and query strategies that stay fast as your data grows — not just on launch day. +

+
+ DatabaseDesign + PostgreSQL + Oracle +
+
+
+
+
+ +

APIs That Don't Break

+

+ Clean, stable REST interfaces designed for concurrency, integrations, and teams that depend on them. +

+
+ RESTApi + BackendEngineering + Symfony +
+
+
+
+
+ +

IoT From Prototype to Production

+

+ Hardware integration, network design, and device reliability — built for real-world deployment, not just demos. +

+
+ IoT + ZWave + EmbeddedSystems +
+
+
+ +
diff --git a/resources/views/components/vades/home/hero.blade.php b/resources/views/components/vades/home/hero.blade.php new file mode 100644 index 0000000..28bed84 --- /dev/null +++ b/resources/views/components/vades/home/hero.blade.php @@ -0,0 +1,11 @@ +
+
+

{{$page->title}}

+

{{$page->excerpt}}

+ Who's Building This +
+
+ +
+
diff --git a/resources/views/components/vades/home/index.blade.php b/resources/views/components/vades/home/index.blade.php new file mode 100644 index 0000000..bd7d14f --- /dev/null +++ b/resources/views/components/vades/home/index.blade.php @@ -0,0 +1,28 @@ + + @if(!empty($page)) + + + + + + + @endif + +
+ +
+ +
+ +
+ + @if($articles->isNotEmpty()) +
+ +
+ @endif + + +
diff --git "a/resources/views/components/widgets/\342\232\241contact-form/contact-form.blade.php" "b/resources/views/components/widgets/\342\232\241contact-form/contact-form.blade.php" new file mode 100644 index 0000000..52ca221 --- /dev/null +++ "b/resources/views/components/widgets/\342\232\241contact-form/contact-form.blade.php" @@ -0,0 +1,54 @@ +
+ @if ($errorMessage) + + @endif + @if (session()->has('success')) + + @endif +
+
+
+ + @error('name') + {{ $message }} + @enderror +
+ +
+ + @error('email') + {{ $message }} + @enderror +
+
+ + @error('message') + {{ $message }} + @enderror +
+
+ +
+
+ +
+
+
+
diff --git "a/resources/views/components/widgets/\342\232\241contact-form/contact-form.php" "b/resources/views/components/widgets/\342\232\241contact-form/contact-form.php" new file mode 100644 index 0000000..f4a5ea8 --- /dev/null +++ "b/resources/views/components/widgets/\342\232\241contact-form/contact-form.php" @@ -0,0 +1,49 @@ + 'required|min:3', + 'email' => 'required|email', + 'message' => 'required|min:5', + ]; + + public function submit(Request $request,DomainManagerService $domainManager) + { + $this->errorMessage = null; + + $this->validate(); + + // Save data + try { + Inquiry::create([ + 'project_id' =>$domainManager->getProjectId(), + 'subject' => 'Contact Form Submission', + 'name' => $this->name, + 'email' => $this->email, + 'message' => $this->message, + 'ip_address' => $request->ip(), + 'user_agent' => $request->userAgent(), + ]); + $this->reset(['name', 'email', 'message']); + + session()->flash('success', __('app.form.successContactForm')); + } catch (\Exception $e) { + $this->errorMessage = __('app.form.errorContactForm'); + } + } + + public function render() + { + return view('components.widgets.⚡contact-form.contact-form'); + } +}; diff --git "a/resources/views/components/widgets/\342\232\241search-suggestion/_search-suggestion.blade.php" "b/resources/views/components/widgets/\342\232\241search-suggestion/_search-suggestion.blade.php" new file mode 100644 index 0000000..36b087b --- /dev/null +++ "b/resources/views/components/widgets/\342\232\241search-suggestion/_search-suggestion.blade.php" @@ -0,0 +1,34 @@ + + + +
+ +
+
+ {{-- The dropdown with the search results. + It is only shown if the query is not empty and there are search results. --}} + @if(!empty($query) && !empty($results)) + + + + @endif +
diff --git "a/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.blade.php" "b/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.blade.php" new file mode 100644 index 0000000..78b8547 --- /dev/null +++ "b/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.blade.php" @@ -0,0 +1,33 @@ + diff --git "a/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.php" "b/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.php" new file mode 100644 index 0000000..1447310 --- /dev/null +++ "b/resources/views/components/widgets/\342\232\241search-suggestion/search-suggestion.php" @@ -0,0 +1,101 @@ +contentType = $contentType; + $this->placeholderText = $placeholderText; + } + /** + * This method is called whenever the 'query' public property is updated. + * It performs a search for products based on the query. + */ + public function updatedQuery() + { + $this->results = Content::published() + ->where(function($query) { + $query->where('title', 'like', '%' . $this->query . '%') + ->orWhere('excerpt', 'like', '%' . $this->query . '%'); + }) + ->whereIn('content_type', $this->contentType) + ->get(); + $this->selectedResult = 0; // Auswahl zurücksetzen bei neuer Suche + } + + public function moveSelectionDown() + { + if (count($this->results) === 0) return; + if ($this->selectedResult < count($this->results) - 1) { + $this->selectedResult++; + } + } + + public function moveSelectionUp() + { + if (count($this->results) === 0) return; + if ($this->selectedResult > 0) { + $this->selectedResult--; + } + } + + /** + * This method is called when a search result is clicked. + * It sets the query to the name of the selected product and clears the search results. + * + * @param int $id The ID of the selected product. + */ + public function selectResult($id = null) + { + try { + // Wenn kein postId übergeben wurde, wähle das aktuell selektierte + if ($id === null && isset($this->results[$this->selectedResult])) { + $id = $this->results[$this->selectedResult]->id; + } + $item = Content::find($id); + \Fruitcake\LaravelDebugbar\Facades\Debugbar::info("Selected item: " . ($item ? $item->title : 'None')); + if ($item) { + + switch ($item->content_type->value) { + case ContentContentType::Page->value: + return redirect()->route('pageItem', $item->slug); + case ContentContentType::Article->value: + + return redirect()->route('articleShow', $item->slug); + case ContentContentType::Place->value: + return redirect()->route('placeShow', $item->slug); + case ContentContentType::Tutorial->value: + + case ContentContentType::Guide->value: + break; + default: + // Do nothing + break; + } + } + } catch (\Throwable $e) { + if (config('app.debug')) { + throw $e; + } + // Im Produktionsmodus: Fehler ignorieren, nichts tun + } + } + + public function render() + { + return view('components.widgets.⚡search-suggestion.search-suggestion'); + } +}; \ No newline at end of file diff --git a/resources/views/errors/401.blade.php b/resources/views/errors/401.blade.php new file mode 100644 index 0000000..5c586db --- /dev/null +++ b/resources/views/errors/401.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Unauthorized')) +@section('code', '401') +@section('message', __('Unauthorized')) diff --git a/resources/views/errors/402.blade.php b/resources/views/errors/402.blade.php new file mode 100644 index 0000000..3bc23ef --- /dev/null +++ b/resources/views/errors/402.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Payment Required')) +@section('code', '402') +@section('message', __('Payment Required')) diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php new file mode 100644 index 0000000..a5506f0 --- /dev/null +++ b/resources/views/errors/403.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Forbidden')) +@section('code', '403') +@section('message', __($exception->getMessage() ?: 'Forbidden')) diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php new file mode 100644 index 0000000..25c55d3 --- /dev/null +++ b/resources/views/errors/404.blade.php @@ -0,0 +1,7 @@ +@extends('errors.layout') +@section('title', __('app.error.404.title')) +@section('code', '404') +@section('content') +

{{__('app.error.404.message')}}

+ {{__('app.nav.takeMeHome')}} +@endsection diff --git a/resources/views/errors/419.blade.php b/resources/views/errors/419.blade.php new file mode 100644 index 0000000..c09216e --- /dev/null +++ b/resources/views/errors/419.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Page Expired')) +@section('code', '419') +@section('message', __('Page Expired')) diff --git a/resources/views/errors/429.blade.php b/resources/views/errors/429.blade.php new file mode 100644 index 0000000..f01b07b --- /dev/null +++ b/resources/views/errors/429.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Too Many Requests')) +@section('code', '429') +@section('message', __('Too Many Requests')) diff --git a/resources/views/errors/500.blade.php b/resources/views/errors/500.blade.php new file mode 100644 index 0000000..e718742 --- /dev/null +++ b/resources/views/errors/500.blade.php @@ -0,0 +1,6 @@ +@extends('errors.layout') +@section('title', __('app.error.500.title')) +@section('code', '500') +@section('content') +

{{__('app.error.500.message')}}

+@endsection \ No newline at end of file diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php new file mode 100644 index 0000000..c5a9dde --- /dev/null +++ b/resources/views/errors/503.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Service Unavailable')) +@section('code', '503') +@section('message', __('Service Unavailable')) diff --git a/resources/views/errors/layout.blade.php b/resources/views/errors/layout.blade.php new file mode 100644 index 0000000..0d8d618 --- /dev/null +++ b/resources/views/errors/layout.blade.php @@ -0,0 +1,35 @@ + + + + @yield('title', 'Error') + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite([$globalCssPath, 'resources/js/app.js']) + @else + @vite([$globalCssPath, 'resources/js/app.js']) + @endif + + +
+
+ + {{-- {{ config('myapp.name') }} --}} +
+
+ +

@yield('title', 'An Error Occurred')

+
+ @yield('content') +
+ + +
+ +@if(app()->environment('local')) +

@yield('code', 'Error')

+
+ {{ $exception->getMessage() }} +
+ @endif +
+ + \ No newline at end of file diff --git a/resources/views/errors/minimal.blade.php b/resources/views/errors/minimal.blade.php new file mode 100644 index 0000000..db69f25 --- /dev/null +++ b/resources/views/errors/minimal.blade.php @@ -0,0 +1,34 @@ + + + + + + + @yield('title') + + + + + + +
+
+
+
+ @yield('code') +
+ +
+ @yield('message') +
+
+
+
+ + diff --git a/resources/views/livewire/.gitkeep b/resources/views/livewire/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/vendor/pagination/bootstrap-4.blade.php b/resources/views/vendor/pagination/bootstrap-4.blade.php new file mode 100644 index 0000000..63c6f56 --- /dev/null +++ b/resources/views/vendor/pagination/bootstrap-4.blade.php @@ -0,0 +1,46 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/bootstrap-5.blade.php b/resources/views/vendor/pagination/bootstrap-5.blade.php new file mode 100644 index 0000000..a1795a4 --- /dev/null +++ b/resources/views/vendor/pagination/bootstrap-5.blade.php @@ -0,0 +1,88 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/default.blade.php b/resources/views/vendor/pagination/default.blade.php new file mode 100644 index 0000000..0db70b5 --- /dev/null +++ b/resources/views/vendor/pagination/default.blade.php @@ -0,0 +1,46 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/semantic-ui.blade.php b/resources/views/vendor/pagination/semantic-ui.blade.php new file mode 100644 index 0000000..ef0dbb1 --- /dev/null +++ b/resources/views/vendor/pagination/semantic-ui.blade.php @@ -0,0 +1,36 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/simple-bootstrap-4.blade.php b/resources/views/vendor/pagination/simple-bootstrap-4.blade.php new file mode 100644 index 0000000..4bb4917 --- /dev/null +++ b/resources/views/vendor/pagination/simple-bootstrap-4.blade.php @@ -0,0 +1,27 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/simple-bootstrap-5.blade.php b/resources/views/vendor/pagination/simple-bootstrap-5.blade.php new file mode 100644 index 0000000..a89005e --- /dev/null +++ b/resources/views/vendor/pagination/simple-bootstrap-5.blade.php @@ -0,0 +1,29 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/simple-default.blade.php b/resources/views/vendor/pagination/simple-default.blade.php new file mode 100644 index 0000000..36bdbc1 --- /dev/null +++ b/resources/views/vendor/pagination/simple-default.blade.php @@ -0,0 +1,19 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/simple-tailwind.blade.php b/resources/views/vendor/pagination/simple-tailwind.blade.php new file mode 100644 index 0000000..ea02400 --- /dev/null +++ b/resources/views/vendor/pagination/simple-tailwind.blade.php @@ -0,0 +1,25 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/resources/views/vendor/pagination/tailwind.blade.php b/resources/views/vendor/pagination/tailwind.blade.php new file mode 100644 index 0000000..3708138 --- /dev/null +++ b/resources/views/vendor/pagination/tailwind.blade.php @@ -0,0 +1,106 @@ +@if ($paginator->hasPages()) + +@endif \ No newline at end of file diff --git a/resources/views/vendor/pagination/tailwind_bck.blade.php b/resources/views/vendor/pagination/tailwind_bck.blade.php new file mode 100644 index 0000000..1ed2667 --- /dev/null +++ b/resources/views/vendor/pagination/tailwind_bck.blade.php @@ -0,0 +1,106 @@ +@if ($paginator->hasPages()) + +@endif \ No newline at end of file diff --git a/routes/console.php b/routes/console.php index 3c9adf1..c09ca04 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,8 +1,10 @@ comment(Inspiring::quote()); -})->purpose('Display an inspiring quote'); +use Illuminate\Support\Facades\Schedule; + +Schedule::command('app:generate-album')->dailyAt('01:00'); +Schedule::command('app:import-project-content')->dailyAt('02:00'); +Schedule::command('app:generate-sitemap ivnbg --path=../domains/ivnbg.com/public_html')->dailyAt('03:00'); +Schedule::command('app:generate-sitemap martinvach --path=../domains/martinvach.com/public_html')->dailyAt('04:00'); +Schedule::command('app:generate-sitemap vades --path=../domains/vades.dev/public_html')->dailyAt('05:00'); diff --git a/routes/default.php b/routes/default.php new file mode 100644 index 0000000..2cd8996 --- /dev/null +++ b/routes/default.php @@ -0,0 +1,43 @@ +name('home'); + +/** + * Pages + */ +Route::get('/pages/{slug}', PageController::class)->name('pageItem'); + +/** + * Blog + */ +Route::get('/blog', [ArticleController::class, 'index'])->name('articleIndex'); + +Route::get('/blog/{slug}', [ArticleController::class, 'show'])->name('articleShow'); + +/** + * Places + */ +Route::get('/place', [PlaceController::class, 'index'])->name('placeIndex'); + +Route::get('/place/{slug}', [PlaceController::class, 'show'])->name('placeShow'); +/** + * Tags + */ +Route::get('/tags/article', [TagController::class, 'index'])->name('tagArticle'); + +/** + * Photo Gallery + */ +Route::get('/photo-gallery', [PhotoGalleryController::class,'index'])->name('photoGalleryIndex'); +Route::get('/photo-gallery/{slug}', [PhotoGalleryController::class,'show'])->name('photoGalleryShow'); \ No newline at end of file diff --git a/routes/ivnbg.php b/routes/ivnbg.php new file mode 100644 index 0000000..7093e3a --- /dev/null +++ b/routes/ivnbg.php @@ -0,0 +1,44 @@ +name('home'); +Route::permanentRedirect('/home', '/'); + +/** + * Pages + */ +Route::get('/pages/{slug}', PageController::class)->name('pageItem'); + +/** + * Blog + */ +Route::get('/blog', [ArticleController::class, 'index'])->name('articleIndex'); + +Route::get('/blog/{slug}', [ArticleController::class, 'show'])->name('articleShow'); + +/** + * Places + */ +Route::get('/place', [PlaceController::class, 'index'])->name('placeIndex'); + +Route::get('/place/{slug}', [PlaceController::class, 'show'])->name('placeShow'); +/** + * Tags + */ +Route::get('/tags/article', [TagController::class, 'index'])->name('tagArticle'); + +/** + * Photo Gallery + */ +Route::get('/photo-gallery', [PhotoGalleryController::class,'index'])->name('photoGalleryIndex'); +Route::get('/photo-gallery/{slug}', [PhotoGalleryController::class,'show'])->name('photoGalleryShow'); \ No newline at end of file diff --git a/routes/martinvach.php b/routes/martinvach.php new file mode 100644 index 0000000..0bbf120 --- /dev/null +++ b/routes/martinvach.php @@ -0,0 +1,45 @@ +name('home'); +Route::permanentRedirect('/home', '/'); + +/** + * Pages + */ +Route::get('/pages/{slug}', PageController::class)->name('pageItem'); + +/** + * Blog + */ +Route::get('/blog', [ArticleController::class, 'index'])->name('articleIndex') ->middleware(AbortWithNotFound::class); + +Route::get('/blog/{slug}', [ArticleController::class, 'show'])->name('articleShow') ->middleware(AbortWithNotFound::class); + +/** + * Places + */ +Route::get('/place', [PlaceController::class, 'index'])->name('placeIndex') ->middleware(AbortWithNotFound::class); + +Route::get('/place/{slug}', [PlaceController::class, 'show'])->name('placeShow') ->middleware(AbortWithNotFound::class); +/** + * Tags + */ +Route::get('/tags/article', [TagController::class, 'index'])->name('tagArticle') ->middleware(AbortWithNotFound::class); + +/** + * Photo Gallery + */ +Route::get('/photo-gallery', [PhotoGalleryController::class,'index'])->name('photoGalleryIndex'); +Route::get('/photo-gallery/{slug}', [PhotoGalleryController::class,'show'])->name('photoGalleryShow'); \ No newline at end of file diff --git a/routes/myprompties.php b/routes/myprompties.php new file mode 100644 index 0000000..b90d64f --- /dev/null +++ b/routes/myprompties.php @@ -0,0 +1,8 @@ +name('home'); diff --git a/routes/vades.php b/routes/vades.php new file mode 100644 index 0000000..f2b8173 --- /dev/null +++ b/routes/vades.php @@ -0,0 +1,45 @@ +name('home'); +Route::permanentRedirect('/home', '/'); + +/** + * Pages + */ +Route::get('/pages/{slug}', PageController::class)->name('pageItem'); + +/** + * Blog + */ +Route::get('/blog', [ArticleController::class, 'index'])->name('articleIndex') ->middleware(AbortWithNotFound::class); + +Route::get('/blog/{slug}', [ArticleController::class, 'show'])->name('articleShow') ->middleware(AbortWithNotFound::class); + +/** + * Places + */ +Route::get('/place', [PlaceController::class, 'index'])->name('placeIndex') ->middleware(AbortWithNotFound::class); + +Route::get('/place/{slug}', [PlaceController::class, 'show'])->name('placeShow') ->middleware(AbortWithNotFound::class); +/** + * Tags + */ +Route::get('/tags/article', [TagController::class, 'index'])->name('tagArticle') ->middleware(AbortWithNotFound::class); + +/** + * Photo Gallery + */ +Route::get('/photo-gallery', [PhotoGalleryController::class,'index'])->name('photoGalleryIndex') ->middleware(AbortWithNotFound::class); +Route::get('/photo-gallery/{slug}', [PhotoGalleryController::class,'show'])->name('photoGalleryShow'); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 59be49a..aa790c5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,8 +1,15 @@ getSlug()}.php"); + +if (file_exists($routeFile)) { + ds("Loading domain route file: {$routeFile}"); + require_once $routeFile; +} else { + ds("Loading fallback route file: routes/default.php"); + require_once base_path('routes/default.php'); +} -Route::get('/', function () { - ds('Welcome to Livewire Volt!'); - return view('welcome'); -})->name('home'); diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore index d6b7ef3..dbe5558 100644 --- a/storage/app/public/.gitignore +++ b/storage/app/public/.gitignore @@ -1,2 +1,4 @@ * -!.gitignore +!images/ +!images/** +!.gitignore \ No newline at end of file diff --git a/storage/app/public/images/bcg-body.jpg b/storage/app/public/images/bcg-body.jpg new file mode 100644 index 0000000..6383adc Binary files /dev/null and b/storage/app/public/images/bcg-body.jpg differ diff --git a/storage/app/public/images/placeholders/article.jpg b/storage/app/public/images/placeholders/article.jpg new file mode 100644 index 0000000..dfe5eba Binary files /dev/null and b/storage/app/public/images/placeholders/article.jpg differ diff --git a/storage/app/public/images/placeholders/page.jpg b/storage/app/public/images/placeholders/page.jpg new file mode 100644 index 0000000..dfe5eba Binary files /dev/null and b/storage/app/public/images/placeholders/page.jpg differ diff --git a/storage/app/public/images/placeholders/place.jpg b/storage/app/public/images/placeholders/place.jpg new file mode 100644 index 0000000..dfe5eba Binary files /dev/null and b/storage/app/public/images/placeholders/place.jpg differ diff --git a/storage/app/public/images/placeholders/place.webp b/storage/app/public/images/placeholders/place.webp new file mode 100644 index 0000000..48e218f Binary files /dev/null and b/storage/app/public/images/placeholders/place.webp differ diff --git a/storage/app/public/images/svg/bars-3.svg b/storage/app/public/images/svg/bars-3.svg new file mode 100644 index 0000000..d22bc0f --- /dev/null +++ b/storage/app/public/images/svg/bars-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/storage/app/public/images/svg/circle-chevron-right.svg b/storage/app/public/images/svg/circle-chevron-right.svg new file mode 100644 index 0000000..f283bce --- /dev/null +++ b/storage/app/public/images/svg/circle-chevron-right.svg @@ -0,0 +1 @@ + diff --git a/storage/app/public/images/svg/close.svg b/storage/app/public/images/svg/close.svg new file mode 100644 index 0000000..59df0d2 --- /dev/null +++ b/storage/app/public/images/svg/close.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/error.svg b/storage/app/public/images/svg/error.svg new file mode 100644 index 0000000..a692855 --- /dev/null +++ b/storage/app/public/images/svg/error.svg @@ -0,0 +1 @@ +bug fixing \ No newline at end of file diff --git a/storage/app/public/images/svg/ivnbg-logo.svg b/storage/app/public/images/svg/ivnbg-logo.svg new file mode 100644 index 0000000..5a37479 --- /dev/null +++ b/storage/app/public/images/svg/ivnbg-logo.svg @@ -0,0 +1,8 @@ + + I + N + B + G + + + \ No newline at end of file diff --git a/storage/app/public/images/svg/laravel-core-logo.svg b/storage/app/public/images/svg/laravel-core-logo.svg new file mode 100644 index 0000000..e4e710e --- /dev/null +++ b/storage/app/public/images/svg/laravel-core-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/storage/app/public/images/svg/martinvach-logo.svg b/storage/app/public/images/svg/martinvach-logo.svg new file mode 100644 index 0000000..532884e --- /dev/null +++ b/storage/app/public/images/svg/martinvach-logo.svg @@ -0,0 +1,14 @@ + + + + V + + + MARTIN VACH + + + + + + Photography + \ No newline at end of file diff --git a/storage/app/public/images/svg/search.svg b/storage/app/public/images/svg/search.svg new file mode 100644 index 0000000..bb7909b --- /dev/null +++ b/storage/app/public/images/svg/search.svg @@ -0,0 +1 @@ + diff --git a/storage/app/public/images/svg/vades-feature-1.svg b/storage/app/public/images/svg/vades-feature-1.svg new file mode 100644 index 0000000..f1bfc24 --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-1.svg @@ -0,0 +1 @@ + diff --git a/storage/app/public/images/svg/vades-feature-ai.svg b/storage/app/public/images/svg/vades-feature-ai.svg new file mode 100644 index 0000000..80ee0d1 --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-feature-api.svg b/storage/app/public/images/svg/vades-feature-api.svg new file mode 100644 index 0000000..bb5edb6 --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-api.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-feature-architecture.svg b/storage/app/public/images/svg/vades-feature-architecture.svg new file mode 100644 index 0000000..1626020 --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-feature-db.svg b/storage/app/public/images/svg/vades-feature-db.svg new file mode 100644 index 0000000..c86d4a0 --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-db.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-feature-full-stack.svg b/storage/app/public/images/svg/vades-feature-full-stack.svg new file mode 100644 index 0000000..4dbfeec --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-full-stack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-feature-iot.svg b/storage/app/public/images/svg/vades-feature-iot.svg new file mode 100644 index 0000000..1be77cf --- /dev/null +++ b/storage/app/public/images/svg/vades-feature-iot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-hero.svg b/storage/app/public/images/svg/vades-hero.svg new file mode 100644 index 0000000..b2b10fd --- /dev/null +++ b/storage/app/public/images/svg/vades-hero.svg @@ -0,0 +1,290 @@ + + + + + + + + + site stats + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/storage/app/public/images/svg/vades-logo-text-light.svg b/storage/app/public/images/svg/vades-logo-text-light.svg new file mode 100644 index 0000000..bce3eba --- /dev/null +++ b/storage/app/public/images/svg/vades-logo-text-light.svg @@ -0,0 +1,31 @@ + + + + + Asset 1 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-logo-text.svg b/storage/app/public/images/svg/vades-logo-text.svg new file mode 100644 index 0000000..a844830 --- /dev/null +++ b/storage/app/public/images/svg/vades-logo-text.svg @@ -0,0 +1,31 @@ + + + + + Asset 1 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storage/app/public/images/svg/vades-logo.svg b/storage/app/public/images/svg/vades-logo.svg new file mode 100644 index 0000000..64ee88b --- /dev/null +++ b/storage/app/public/images/svg/vades-logo.svg @@ -0,0 +1 @@ +symbol \ No newline at end of file diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/debugbar/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Feature/Settings/TwoFactorAuthenticationTest.php b/tests/Feature/Settings/TwoFactorAuthenticationTest.php deleted file mode 100644 index 1ca2df9..0000000 --- a/tests/Feature/Settings/TwoFactorAuthenticationTest.php +++ /dev/null @@ -1,86 +0,0 @@ -markTestSkipped('Two-factor authentication is not enabled.'); - } - - Features::twoFactorAuthentication([ - 'confirm' => true, - 'confirmPassword' => true, - ]); - } - - public function test_two_factor_settings_page_can_be_rendered(): void - { - $user = User::factory()->withoutTwoFactor()->create(); - - $this->actingAs($user) - ->withSession(['auth.password_confirmed_at' => time()]) - ->get(route('two-factor.show')) - ->assertOk() - ->assertSee('Two Factor Authentication') - ->assertSee('Disabled'); - } - - public function test_two_factor_settings_page_requires_password_confirmation_when_enabled(): void - { - $user = User::factory()->create(); - - $response = $this->actingAs($user) - ->get(route('two-factor.show')); - - $response->assertRedirect(route('password.confirm')); - } - - public function test_two_factor_settings_page_returns_forbidden_response_when_two_factor_is_disabled(): void - { - config(['fortify.features' => []]); - - $user = User::factory()->create(); - - $response = $this->actingAs($user) - ->withSession(['auth.password_confirmed_at' => time()]) - ->get(route('two-factor.show')); - - $response->assertForbidden(); - } - - public function test_two_factor_authentication_disabled_when_confirmation_abandoned_between_requests(): void - { - $user = User::factory()->create(); - - $user->forceFill([ - 'two_factor_secret' => encrypt('test-secret'), - 'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])), - 'two_factor_confirmed_at' => null, - ])->save(); - - $this->actingAs($user); - - $component = Volt::test('settings.two-factor'); - - $component->assertSet('twoFactorEnabled', false); - - $this->assertDatabaseHas('users', [ - 'id' => $user->id, - 'two_factor_secret' => null, - 'two_factor_recovery_codes' => null, - ]); - } -} diff --git a/tests/Unit/AppProjectTest.php b/tests/Unit/AppProjectTest.php new file mode 100644 index 0000000..a547c97 --- /dev/null +++ b/tests/Unit/AppProjectTest.php @@ -0,0 +1,13 @@ +getUrl())->toBe('https://www.ivnbg.com'); + expect(AppProject::MartinVach->getUrl())->toBe('https://www.martinvach.com'); + expect(AppProject::MyPrompties->getUrl())->toBe('https://www.myprompties.com'); + expect(AppProject::Vades->getUrl())->toBe('https://www.vades.dev'); + expect(AppProject::Aitomatix->getUrl())->toBe('https://www.aitomatix.com'); + expect(AppProject::AitomatixCz->getUrl())->toBe('https://www.aitomatix.cz'); + expect(AppProject::LaravelCore->getUrl())->toBe('https://www.laravel-core-test.com'); +}); \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index f65249e..5e160cf 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,19 +3,66 @@ import { } from 'vite'; import laravel from 'laravel-vite-plugin'; import tailwindcss from "@tailwindcss/vite"; +import path from 'path'; +import dotenv from 'dotenv'; -export default defineConfig({ - plugins: [ - laravel({ - input: ['resources/css/app.css', 'resources/js/app.js'], - refresh: true, - }), - tailwindcss(), - ], - server: { - cors: true, - watch: { - ignored: ['**/storage/framework/views/**'], +// Load .env into process.env before anything else +dotenv.config(); +export default defineConfig(() => { + // Check if SITE is defined + //const site = process.env.MY_PROJECT_SLUG; + const site = process.env.npm_config_site || process.env.MY_PROJECT_SLUG || undefined; + console.log(`SITE environment variable: ${site}`); + + let mappedDomain; + + switch (site) { + case 'ivnbg': + mappedDomain = 'ivnbg.com'; + break; + case 'vades': + mappedDomain = 'vades.dev'; + break; + case 'martinvach': + mappedDomain = 'martinvach.com'; + break; + default: + mappedDomain = site; + break; + } + + const publicDir = mappedDomain + ? path.resolve(__dirname, '..', 'domains', mappedDomain, 'public_html') + : 'public'; + + const domainResourceDir = site || 'default'; + + console.log(`Using public directory: ${publicDir}`); + + return { + plugins: [ + laravel({ + input: ['resources/css/'+ domainResourceDir + '/app.css', 'resources/js/app.js'], + refresh: true, + + // Pass the dynamic directory here + publicDirectory: publicDir, + + // Keep this as 'build'. It will be appended to whichever publicDir is active. + buildDirectory: 'build', + }), + tailwindcss(), + ], + build: { + // Important: Allow Vite to empty a directory that is outside + // the root of the current project + emptyOutDir: true, + }, + server: { + cors: true, + watch: { + ignored: ['**/storage/framework/views/**'], + }, }, - }, + }; });