diff --git a/app/Common.php b/app/Common.php
index 6f720768acb7053f8312b65ba3d086d1b3aab598..89981c0ddf3cb900212b969d3546b2e1dda7b409 100644
--- a/app/Common.php
+++ b/app/Common.php
@@ -37,7 +37,7 @@ if (! function_exists('view')) {
         $renderer = single_service('renderer', $path);
 
         $saveData = config('View')
-->saveData;
+            ->saveData;
 
         if (array_key_exists('saveData', $options)) {
             $saveData = (bool) $options['saveData'];
diff --git a/app/Config/Filters.php b/app/Config/Filters.php
index 7e65d2967775a0f0afb95a4eb33a4d22769805fb..85ab79abcebf94bbe7281b8fcec99fefa7fc0a6a 100644
--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -67,7 +67,7 @@ class Filters extends BaseConfig
     /**
      * List of filter aliases that are always applied before and after every request.
      *
-     * @var array<string, array<string, array<string, string|array<string>>>>>|array<string, list<string>>
+     * @var array<string, array<string, array<string, string|array<string>>>>|array<string, list<string>>
      */
     public array $globals = [
         'before' => [
diff --git a/app/Config/Generators.php b/app/Config/Generators.php
index 172b40815d617906d9ad4f032342fbeb67ec8cb6..7957d64e1fab6520e040985384e51864ea8039de 100644
--- a/app/Config/Generators.php
+++ b/app/Config/Generators.php
@@ -25,7 +25,7 @@ class Generators extends BaseConfig
      *
      * YOU HAVE BEEN WARNED!
      *
-     * @var array<string, string>
+     * @var array<string, string|array<string,string>>
      */
     public array $views = [
         'make:cell' => [
diff --git a/app/Config/Services.php b/app/Config/Services.php
index b12e40a84b2acde7934d7a6d4a3ee406e357951b..52f8233cc6f9503174bf9bfee8dca5d766daae4d 100644
--- a/app/Config/Services.php
+++ b/app/Config/Services.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Config;
 
 use App\Libraries\Breadcrumb;
+use App\Libraries\HtmlHead;
 use App\Libraries\Negotiate;
 use App\Libraries\Router;
 use CodeIgniter\Config\BaseService;
@@ -66,4 +67,13 @@ class Services extends BaseService
 
         return new Breadcrumb();
     }
+
+    public static function html_head(bool $getShared = true): HtmlHead
+    {
+        if ($getShared) {
+            return self::getSharedInstance('html_head');
+        }
+
+        return new HtmlHead();
+    }
 }
diff --git a/app/Config/View.php b/app/Config/View.php
index c43985de570c139854d1f57b25e9f32c060c7011..7225324cb8d2c07c292258d0a73adb1d6d86751a 100644
--- a/app/Config/View.php
+++ b/app/Config/View.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace Config;
 
-use App\Views\Decorators\SiteHead;
 use CodeIgniter\Config\View as BaseView;
 use CodeIgniter\View\ViewDecoratorInterface;
 use ViewComponents\Decorator;
@@ -54,5 +53,5 @@ class View extends BaseView
      *
      * @var list<class-string<ViewDecoratorInterface>>
      */
-    public array $decorators = [Decorator::class, SiteHead::class];
+    public array $decorators = [Decorator::class];
 }
diff --git a/app/Controllers/ActorController.php b/app/Controllers/ActorController.php
index bb879be25c560e529df39efc5f7e506b4314ae6c..233c5f4457d679e42f71e32adcc5da923c8d9f8f 100644
--- a/app/Controllers/ActorController.php
+++ b/app/Controllers/ActorController.php
@@ -31,10 +31,10 @@ class ActorController extends FediverseActorController
         }
 
         helper(['form', 'components', 'svg']);
+        // @phpstan-ignore-next-line
+        set_follow_metatags($this->actor);
         $data = [
-            // @phpstan-ignore-next-line
-            'metatags' => get_follow_metatags($this->actor),
-            'actor'    => $this->actor,
+            'actor' => $this->actor,
         ];
 
         return view('podcast/follow', $data);
diff --git a/app/Controllers/CreditsController.php b/app/Controllers/CreditsController.php
index 24463f71835682a6ec66993bbe17f9adb274019d..e4e7fe8bab0bafb55c969e0cf8bf9cf00a7815a7 100644
--- a/app/Controllers/CreditsController.php
+++ b/app/Controllers/CreditsController.php
@@ -164,10 +164,10 @@ class CreditsController extends BaseController
                 }
             }
 
+            set_page_metatags($page);
             $data = [
-                'metatags' => get_page_metatags($page),
-                'page'     => $page,
-                'credits'  => $credits,
+                'page'    => $page,
+                'credits' => $credits,
             ];
 
             $found = view('pages/credits', $data);
diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php
index 87f2864e1341f33dbf5ff63c2d412a67e384b303..673b370f94213529cb3a6c8b49248f2c006a4c9e 100644
--- a/app/Controllers/EpisodeCommentController.php
+++ b/app/Controllers/EpisodeCommentController.php
@@ -96,12 +96,12 @@ class EpisodeCommentController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
+            set_episode_comment_metatags($this->comment);
             $data = [
-                'metatags' => get_episode_comment_metatags($this->comment),
-                'podcast'  => $this->podcast,
-                'actor'    => $this->actor,
-                'episode'  => $this->episode,
-                'comment'  => $this->comment,
+                'podcast' => $this->podcast,
+                'actor'   => $this->actor,
+                'episode' => $this->episode,
+                'comment' => $this->comment,
             ];
 
             // if user is logged in then send to the authenticated activity view
diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php
index 335d96d882a3c30b673e13280f49283494dc6efe..c1ceb9fd93edb475878c3f76c6244873581a816a 100644
--- a/app/Controllers/EpisodeController.php
+++ b/app/Controllers/EpisodeController.php
@@ -86,10 +86,10 @@ class EpisodeController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
+            set_episode_metatags($this->episode);
             $data = [
-                'metatags' => get_episode_metatags($this->episode),
-                'podcast'  => $this->podcast,
-                'episode'  => $this->episode,
+                'podcast' => $this->podcast,
+                'episode' => $this->episode,
             ];
 
             $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@@ -135,10 +135,10 @@ class EpisodeController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
+            set_episode_metatags($this->episode);
             $data = [
-                'metatags' => get_episode_metatags($this->episode),
-                'podcast'  => $this->podcast,
-                'episode'  => $this->episode,
+                'podcast' => $this->podcast,
+                'episode' => $this->episode,
             ];
 
             $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@@ -184,13 +184,13 @@ class EpisodeController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
-            // get chapters from json file
+            set_episode_metatags($this->episode);
             $data = [
-                'metatags' => get_episode_metatags($this->episode),
-                'podcast'  => $this->podcast,
-                'episode'  => $this->episode,
+                'podcast' => $this->podcast,
+                'episode' => $this->episode,
             ];
 
+            // get chapters from json file
             if (isset($this->episode->chapters->file_key)) {
                 /** @var FileManagerInterface $fileManager */
                 $fileManager = service('file_manager');
@@ -243,13 +243,13 @@ class EpisodeController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
-            // get transcript from json file
+            set_episode_metatags($this->episode);
             $data = [
-                'metatags' => get_episode_metatags($this->episode),
-                'podcast'  => $this->podcast,
-                'episode'  => $this->episode,
+                'podcast' => $this->podcast,
+                'episode' => $this->episode,
             ];
 
+            // get transcript from json file
             if ($this->episode->transcript !== null) {
                 $data['transcript'] = $this->episode->transcript;
 
diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php
index 515c0d0b5537bf6d082834425d3a8f48ff68a32a..a148ec6cd68fc79b7548c8e093b513288821495d 100644
--- a/app/Controllers/HomeController.php
+++ b/app/Controllers/HomeController.php
@@ -32,9 +32,9 @@ class HomeController extends BaseController
             return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
         }
 
+        set_home_metatags();
         // default behavior: list all podcasts on home page
         $data = [
-            'metatags' => get_home_metatags(),
             'podcasts' => $allPodcasts,
             'sortBy'   => $sortBy,
         ];
diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php
index 83c03566211123894ad186c6732a8ea454578711..d41168f80ff2bf6e56be7b297f8cf7cd5fd0e32a 100644
--- a/app/Controllers/PageController.php
+++ b/app/Controllers/PageController.php
@@ -49,9 +49,9 @@ class PageController extends BaseController
         );
 
         if (! ($found = cache($cacheName))) {
+            set_page_metatags($this->page);
             $data = [
-                'metatags' => get_page_metatags($this->page),
-                'page'     => $this->page,
+                'page' => $this->page,
             ];
 
             $found = view('pages/page', $data);
diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php
index 46a42c1f58d63c1c1daa36a63d293b44abf73154..aaf60ce050c1fed722a9eda5ae35d3d3fadf440b 100644
--- a/app/Controllers/PodcastController.php
+++ b/app/Controllers/PodcastController.php
@@ -78,10 +78,10 @@ class PodcastController extends BaseController
         );
 
         if (! ($cachedView = cache($cacheName))) {
+            set_podcast_metatags($this->podcast, 'activity');
             $data = [
-                'metatags' => get_podcast_metatags($this->podcast, 'activity'),
-                'podcast'  => $this->podcast,
-                'posts'    => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
+                'podcast' => $this->podcast,
+                'posts'   => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
             ];
 
             // if user is logged in then send to the authenticated activity view
@@ -128,10 +128,10 @@ class PodcastController extends BaseController
         if (! ($cachedView = cache($cacheName))) {
             $stats = (new EpisodeModel())->getPodcastStats($this->podcast->id);
 
+            set_podcast_metatags($this->podcast, 'about');
             $data = [
-                'metatags' => get_podcast_metatags($this->podcast, 'about'),
-                'podcast'  => $this->podcast,
-                'stats'    => $stats,
+                'podcast' => $this->podcast,
+                'stats'   => $stats,
             ];
 
             // // if user is logged in then send to the authenticated activity view
@@ -245,8 +245,8 @@ class PodcastController extends BaseController
                 ];
             }
 
+            set_podcast_metatags($this->podcast, 'episodes');
             $data = [
-                'metatags'    => get_podcast_metatags($this->podcast, 'episodes'),
                 'podcast'     => $this->podcast,
                 'episodesNav' => $episodesNavigation,
                 'activeQuery' => $activeQuery,
@@ -315,9 +315,9 @@ class PodcastController extends BaseController
 
     public function links(): string
     {
+        set_podcast_metatags($this->podcast, 'links');
         return view('podcast/links', [
-            'metatags' => get_podcast_metatags($this->podcast, 'links'),
-            'podcast'  => $this->podcast,
+            'podcast' => $this->podcast,
         ]);
     }
 }
diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php
index 42df121f590d92ac905e0a2ffb5725c02677cd94..27eb5f776dd9357b98ee7a9fc4f9948f7534d9b9 100644
--- a/app/Controllers/PostController.php
+++ b/app/Controllers/PostController.php
@@ -45,6 +45,7 @@ class PostController extends FediversePostController
     #[Override]
     public function _remap(string $method, string ...$params): mixed
     {
+
         if (
             ! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast
         ) {
@@ -54,16 +55,21 @@ class PostController extends FediversePostController
         $this->podcast = $podcast;
         $this->actor = $this->podcast->actor;
 
+        if (count($params) <= 1) {
+            throw PageNotFoundException::forPageNotFound();
+        }
+
         if (
-            count($params) > 1 &&
-            ($post = (new PostModel())->getPostById($params[1])) instanceof CastopodPost
+            ! ($post = (new PostModel())->getPostById($params[1])) instanceof CastopodPost
         ) {
-            $this->post = $post;
-
-            unset($params[0]);
-            unset($params[1]);
+            throw PageNotFoundException::forPageNotFound();
         }
 
+        $this->post = $post;
+
+        unset($params[0]);
+        unset($params[1]);
+
         return $this->{$method}(...$params);
     }
 
@@ -74,10 +80,6 @@ class PostController extends FediversePostController
             $this->registerPodcastWebpageHit($this->podcast->id);
         }
 
-        if (! $this->post instanceof CastopodPost) {
-            throw PageNotFoundException::forPageNotFound();
-        }
-
         $cacheName = implode(
             '_',
             array_filter([
@@ -91,10 +93,10 @@ class PostController extends FediversePostController
         );
 
         if (! ($cachedView = cache($cacheName))) {
+            set_post_metatags($this->post);
             $data = [
-                'metatags' => get_post_metatags($this->post),
-                'post'     => $this->post,
-                'podcast'  => $this->podcast,
+                'post'    => $this->post,
+                'podcast' => $this->podcast,
             ];
 
             // if user is logged in then send to the authenticated activity view
@@ -254,12 +256,12 @@ class PostController extends FediversePostController
             $this->registerPodcastWebpageHit($this->podcast->id);
         }
 
+        set_remote_actions_metatags($this->post, $action);
         $data = [
-            'metatags' => get_remote_actions_metatags($this->post, $action),
-            'podcast'  => $this->podcast,
-            'actor'    => $this->actor,
-            'post'     => $this->post,
-            'action'   => $action,
+            'podcast' => $this->podcast,
+            'actor'   => $this->actor,
+            'post'    => $this->post,
+            'action'  => $action,
         ];
 
         helper('form');
diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php
index d8eab7bc036c1db85bd0926c95d704c3f005a775..5279dc4b49e205f5be97efb69b9409c9e8c9389b 100644
--- a/app/Entities/Clip/BaseClip.php
+++ b/app/Entities/Clip/BaseClip.php
@@ -36,7 +36,7 @@ use Modules\Media\Models\MediaModel;
  * @property string $type
  * @property int|null $media_id
  * @property Video|Audio|null $media
- * @property array|null $metadata
+ * @property array<mixed>|null $metadata
  * @property string $status
  * @property string $logs
  * @property User $user
@@ -136,7 +136,7 @@ class BaseClip extends Entity
             $media = new Audio([
                 'file_key'      => $fileKey,
                 'language_code' => $this->getPodcast()
-->language_code,
+                    ->language_code,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
diff --git a/app/Entities/Clip/VideoClip.php b/app/Entities/Clip/VideoClip.php
index 889140aaf6a31ad04beff1f595cc9edd054910a0..d82e5a6b6858bce840e56ec74d1b537ed1c1755a 100644
--- a/app/Entities/Clip/VideoClip.php
+++ b/app/Entities/Clip/VideoClip.php
@@ -16,7 +16,7 @@ use Modules\Media\Models\MediaModel;
 use Override;
 
 /**
- * @property array $theme
+ * @property array{name:string,preview:string} $theme
  * @property string $format
  */
 class VideoClip extends BaseClip
@@ -37,7 +37,7 @@ class VideoClip extends BaseClip
     }
 
     /**
-     * @param array<string, string> $theme
+     * @param array{name:string,preview:string} $theme
      */
     public function setTheme(array $theme): self
     {
@@ -75,7 +75,7 @@ class VideoClip extends BaseClip
         $video = new Video([
             'file_key'      => $fileKey,
             'language_code' => $this->getPodcast()
-->language_code,
+                ->language_code,
             'uploaded_by' => $this->attributes['created_by'],
             'updated_by'  => $this->attributes['created_by'],
         ]);
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index 38604f69bd76aa4fca23e8f4b8aa7924ba7577c7..e9e8bcfae54dda0a4176d4059fb02ea0b9adfcc6 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -191,10 +191,10 @@ class Episode extends Entity
         $this->audio_url = url_to(
             'episode-audio',
             $this->getPodcast()
-->handle,
+                ->handle,
             $this->slug,
             $this->getAudio()
-->file_extension
+                ->file_extension
         );
 
         $this->audio_opengraph_url = $this->audio_url . '?_from=-+Open+Graph+-';
@@ -219,7 +219,7 @@ class Episode extends Entity
             $cover = new Image([
                 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(),
                 'sizes'    => config('Images')
-->podcastCoverSizes,
+                    ->podcastCoverSizes,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
@@ -302,7 +302,7 @@ class Episode extends Entity
             $transcript = new Transcript([
                 'file_key'      => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(),
                 'language_code' => $this->getPodcast()
-->language_code,
+                    ->language_code,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
@@ -339,7 +339,7 @@ class Episode extends Entity
             $chapters = new Chapters([
                 'file_key'      => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(),
                 'language_code' => $this->getPodcast()
-->language_code,
+                    ->language_code,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
diff --git a/app/Entities/Person.php b/app/Entities/Person.php
index 961f56037ceead6778485e7975afff9446d5566c..8da212d0f40334cee6e26140fc9b07c4f9468ff6 100644
--- a/app/Entities/Person.php
+++ b/app/Entities/Person.php
@@ -72,7 +72,7 @@ class Person extends Entity
             $avatar = new Image([
                 'file_key' => 'persons/' . $this->attributes['unique_name'] . '.' . $file->getExtension(),
                 'sizes'    => config('Images')
-->personAvatarSizes,
+                    ->personAvatarSizes,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php
index f39c2826a6daa47ab8e66b220ad4b542e44d8ff6..eabb8ee2a92bb0211526e6c7f859a79e38b4f0f0 100644
--- a/app/Entities/Podcast.php
+++ b/app/Entities/Podcast.php
@@ -231,7 +231,7 @@ class Podcast extends Entity
             $cover = new Image([
                 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/cover.' . $file->getExtension(),
                 'sizes'    => config('Images')
-->podcastCoverSizes,
+                    ->podcastCoverSizes,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
@@ -274,7 +274,7 @@ class Podcast extends Entity
             $banner = new Image([
                 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/banner.' . $file->getExtension(),
                 'sizes'    => config('Images')
-->podcastBannerSizes,
+                    ->podcastBannerSizes,
                 'uploaded_by' => $this->attributes['updated_by'],
                 'updated_by'  => $this->attributes['updated_by'],
             ]);
diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index 2ca9f7a48214b6e5c24ffaad1931dc0fad4636b0..24a196dbc3cc68511d909d5db98858ca70590630 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -105,7 +105,7 @@ if (! function_exists('publication_pill')) {
 
         $label = lang('Episode.publication_status.' . $publicationStatus);
 
-        // @icon('error-warning-fill')
+        // @icon("error-warning-fill")
         return '<x-Pill ' . ($title === '' ? '' : 'title="' . $title . '"') . ' variant="' . $variant . '" class="' . $customClass .
             '">' . $label . ($publicationStatus === 'with_podcast' ? icon('error-warning-fill', [
                 'class' => 'flex-shrink-0 ml-1 text-lg',
@@ -129,20 +129,20 @@ if (! function_exists('publication_button')) {
                 $label = lang('Episode.publish');
                 $route = route_to('episode-publish', $podcastId, $episodeId);
                 $variant = 'primary';
-                $iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
+                $iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
                 break;
             case 'with_podcast':
             case 'scheduled':
                 $label = lang('Episode.publish_edit');
                 $route = route_to('episode-publish_edit', $podcastId, $episodeId);
                 $variant = 'warning';
-                $iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
+                $iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
                 break;
             case 'published':
                 $label = lang('Episode.unpublish');
                 $route = route_to('episode-unpublish', $podcastId, $episodeId);
                 $variant = 'danger';
-                $iconLeft = 'cloud-off-fill'; // @icon('cloud-off-fill')
+                $iconLeft = 'cloud-off-fill'; // @icon("cloud-off-fill")
                 break;
             default:
                 $label = '';
diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php
index b26fab00d0e8245262dbd35b41109a93ac2bbd12..a64807cdd33c98d57dff97329b6eae295dc18005 100644
--- a/app/Helpers/misc_helper.php
+++ b/app/Helpers/misc_helper.php
@@ -210,7 +210,7 @@ if (! function_exists('get_podcast_banner')) {
                 )->podcastBannerDefaultPaths['default'];
 
             $sizes = config('Images')
-->podcastBannerSizes;
+                ->podcastBannerSizes;
 
             $sizeConfig = $sizes[$size];
             helper('filesystem');
@@ -231,7 +231,7 @@ if (! function_exists('get_podcast_banner_mimetype')) {
     {
         if (! $podcast->banner instanceof Image) {
             $sizes = config('Images')
-->podcastBannerSizes;
+                ->podcastBannerSizes;
 
             $sizeConfig = $sizes[$size];
             helper('filesystem');
@@ -252,10 +252,10 @@ if (! function_exists('get_avatar_url')) {
     {
         if (! $person->avatar instanceof Image) {
             $defaultAvatarPath = config('Images')
-->avatarDefaultPath;
+                ->avatarDefaultPath;
 
             $sizes = config('Images')
-->personAvatarSizes;
+                ->personAvatarSizes;
 
             $sizeConfig = $sizes[$size];
 
diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php
index da39f1c76a89a694440c1c8c05a901f5fd86c754..4c57d1ab4751efaf3f0104621dde87a491fd0bba 100644
--- a/app/Helpers/rss_helper.php
+++ b/app/Helpers/rss_helper.php
@@ -422,7 +422,7 @@ if (! function_exists('add_category_tag')) {
     /**
      * Adds <itunes:category> and <category> tags to node for a given category
      */
-    function add_category_tag(SimpleXMLElement $node, Category $category): void
+    function add_category_tag(RssFeed $node, Category $category): void
     {
         $itunesCategory = $node->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
         $itunesCategory->addAttribute(
@@ -441,70 +441,3 @@ if (! function_exists('add_category_tag')) {
         $node->addChild('category', $category->apple_category);
     }
 }
-
-if (! function_exists('rss_to_array')) {
-    /**
-     * Converts XML to array
-     *
-     * FIXME: param should be SimpleRSSElement
-     *
-     * @return array<string, mixed>
-     */
-    function rss_to_array(SimpleXMLElement $rssNode): array
-    {
-        $nameSpaces = ['', 'http://www.itunes.com/dtds/podcast-1.0.dtd', 'https://podcastindex.org/namespace/1.0'];
-        $arrayNode = [];
-        $arrayNode['name'] = $rssNode->getName();
-        $arrayNode['namespace'] = $rssNode->getNamespaces(false);
-        foreach ($rssNode->attributes() as $key => $value) {
-            $arrayNode['attributes'][$key] = (string) $value;
-        }
-
-        $textcontent = trim((string) $rssNode);
-        if (strlen($textcontent) > 0) {
-            $arrayNode['content'] = $textcontent;
-        }
-
-        foreach ($nameSpaces as $currentNameSpace) {
-            foreach ($rssNode->children($currentNameSpace) as $childXmlNode) {
-                $arrayNode['elements'][] = rss_to_array($childXmlNode);
-            }
-        }
-
-        return $arrayNode;
-    }
-}
-
-if (! function_exists('array_to_rss')) {
-    /**
-     * Inserts array (converted to XML node) in XML node
-     *
-     * @param array<string, mixed> $arrayNode
-     * @param RssFeed $xmlNode The XML parent node where this arrayNode should be attached
-     */
-    function array_to_rss(array $arrayNode, RssFeed &$xmlNode): RssFeed
-    {
-        if (array_key_exists('elements', $arrayNode)) {
-            foreach ($arrayNode['elements'] as $childArrayNode) {
-                $childXmlNode = $xmlNode->addChild(
-                    $childArrayNode['name'],
-                    $childArrayNode['content'] ?? null,
-                    $childArrayNode['namespace'] === []
-                        ? null
-                        : current($childArrayNode['namespace'])
-                );
-                if (array_key_exists('attributes', $childArrayNode)) {
-                    foreach (
-                        $childArrayNode['attributes'] as $attributeKey => $attributeValue
-                    ) {
-                        $childXmlNode->addAttribute($attributeKey, $attributeValue);
-                    }
-                }
-
-                array_to_rss($childArrayNode, $childXmlNode);
-            }
-        }
-
-        return $xmlNode;
-    }
-}
diff --git a/app/Helpers/seo_helper.php b/app/Helpers/seo_helper.php
index 9076cc3b8c3a7d896ac0541214e01ec2dfbf7a0c..2418ae38ed9270ea8f32c91e931f0f0ef9f8ca00 100644
--- a/app/Helpers/seo_helper.php
+++ b/app/Helpers/seo_helper.php
@@ -8,19 +8,19 @@ use App\Entities\EpisodeComment;
 use App\Entities\Page;
 use App\Entities\Podcast;
 use App\Entities\Post;
-use Melbahja\Seo\MetaTags;
+use App\Libraries\HtmlHead;
 use Melbahja\Seo\Schema;
 use Melbahja\Seo\Schema\Thing;
 use Modules\Fediverse\Entities\PreviewCard;
 
 /**
- * @copyright  2021 Ad Aures
+ * @copyright  2024 Ad Aures
  * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
  * @link       https://castopod.org/
  */
 
-if (! function_exists('get_podcast_metatags')) {
-    function get_podcast_metatags(Podcast $podcast, string $page): string
+if (! function_exists('set_podcast_metatags')) {
+    function set_podcast_metatags(Podcast $podcast, string $page): void
     {
         $category = '';
         if ($podcast->category->parent_id !== null) {
@@ -48,10 +48,11 @@ if (! function_exists('get_podcast_metatags')) {
             ])
         );
 
-        $metatags = new MetaTags();
+        /** @var HtmlHead $head */
+        $head = service('html_head');
 
-        $metatags
-            ->title($podcast->title . ' (@' . $podcast->handle . ') • ' . lang('Podcast.' . $page))
+        $head
+            ->title(sprintf('%s (@%s) • %s', $podcast->title, $podcast->handle, lang('Podcast.' . $page)))
             ->description(esc($podcast->description))
             ->image((string) $podcast->cover->og_url)
             ->canonical((string) current_url())
@@ -59,20 +60,18 @@ if (! function_exists('get_podcast_metatags')) {
             ->og('image:height', (string) config('Images')->podcastCoverSizes['og']['height'])
             ->og('locale', $podcast->language_code)
             ->og('site_name', esc(service('settings')->get('App.siteName')))
-            ->push('link', [
+            ->tag('link', null, [
                 'rel'  => 'alternate',
                 'type' => 'application/activity+json',
                 'href' => url_to('podcast-activity', esc($podcast->handle)),
-            ]);
-
-        return '<link type="application/rss+xml" rel="alternate" title="' . esc(
-            $podcast->title
-        ) . '" href="' . $podcast->feed_url . '" />' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString();
+            ])->appendRawContent('<link type="application/rss+xml" rel="alternate" title="' . esc(
+                $podcast->title
+            ) . '" href="' . $podcast->feed_url . '" />' . $schema);
     }
 }
 
-if (! function_exists('get_episode_metatags')) {
-    function get_episode_metatags(Episode $episode): string
+if (! function_exists('set_episode_metatags')) {
+    function set_episode_metatags(Episode $episode): void
     {
         $schema = new Schema(
             new Thing('PodcastEpisode', [
@@ -80,7 +79,7 @@ if (! function_exists('get_episode_metatags')) {
                 'name'            => $episode->title,
                 'image'           => $episode->cover->feed_url,
                 'description'     => $episode->description,
-                'datePublished'   => $episode->published_at->format(DATE_ISO8601),
+                'datePublished'   => $episode->published_at->format(DATE_ATOM),
                 'timeRequired'    => iso8601_duration($episode->audio->duration),
                 'duration'        => iso8601_duration($episode->audio->duration),
                 'associatedMedia' => new Thing('MediaObject', [
@@ -93,9 +92,10 @@ if (! function_exists('get_episode_metatags')) {
             ])
         );
 
-        $metatags = new MetaTags();
+        /** @var HtmlHead $head */
+        $head = service('html_head');
 
-        $metatags
+        $head
             ->title($episode->title)
             ->description(esc($episode->description))
             ->image((string) $episode->cover->og_url, 'player')
@@ -106,35 +106,34 @@ if (! function_exists('get_episode_metatags')) {
             ->og('locale', $episode->podcast->language_code)
             ->og('audio', $episode->audio_opengraph_url)
             ->og('audio:type', $episode->audio->file_mimetype)
-            ->meta('article:published_time', $episode->published_at->format(DATE_ISO8601))
-            ->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601))
+            ->meta('article:published_time', $episode->published_at->format(DATE_ATOM))
+            ->meta('article:modified_time', $episode->updated_at->format(DATE_ATOM))
             ->twitter('audio:partner', $episode->podcast->publisher ?? '')
             ->twitter('audio:artist_name', esc($episode->podcast->owner_name))
             ->twitter('player', $episode->getEmbedUrl('light'))
             ->twitter('player:width', (string) config('Embed')->width)
             ->twitter('player:height', (string) config('Embed')->height)
-            ->push('link', [
+            ->tag('link', null, [
                 'rel'  => 'alternate',
                 'type' => 'application/activity+json',
-                'href' => url_to('episode', $episode->podcast->handle, $episode->slug),
-            ]);
-
-        return $metatags->__toString() . PHP_EOL . '<link rel="alternate" type="application/json+oembed" href="' . base_url(
-            route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
-        ) . '" title="' . esc(
-            $episode->title
-        ) . ' oEmbed json" />' . PHP_EOL . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
-            route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug)
-        ) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . PHP_EOL . $schema->__toString();
+                'href' => $episode->link,
+            ])
+            ->appendRawContent('<link rel="alternate" type="application/json+oembed" href="' . base_url(
+                route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
+            ) . '" title="' . esc(
+                $episode->title
+            ) . ' oEmbed json" />' . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
+                route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug)
+            ) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . $schema);
     }
 }
 
-if (! function_exists('get_post_metatags')) {
-    function get_post_metatags(Post $post): string
+if (! function_exists('set_post_metatags')) {
+    function set_post_metatags(Post $post): void
     {
         $socialMediaPosting = new Thing('SocialMediaPosting', [
             '@id'           => url_to('post', esc($post->actor->username), $post->id),
-            'datePublished' => $post->published_at->format(DATE_ISO8601),
+            'datePublished' => $post->published_at->format(DATE_ATOM),
             'author'        => new Thing('Person', [
                 'name' => $post->actor->display_name,
                 'url'  => $post->actor->uri,
@@ -162,8 +161,10 @@ if (! function_exists('get_post_metatags')) {
 
         $schema = new Schema($socialMediaPosting);
 
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+
+        $head
             ->title(lang('Post.title', [
                 'actorDisplayName' => $post->actor->display_name,
             ]))
@@ -171,18 +172,16 @@ if (! function_exists('get_post_metatags')) {
             ->image($post->actor->avatar_image_url)
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')))
-            ->push('link', [
+            ->tag('link', null, [
                 'rel'  => 'alternate',
                 'type' => 'application/activity+json',
                 'href' => url_to('post', esc($post->actor->username), $post->id),
-            ]);
-
-        return $metatags->__toString() . PHP_EOL . $schema->__toString();
+            ])->appendRawContent((string) $schema);
     }
 }
 
-if (! function_exists('get_episode_comment_metatags')) {
-    function get_episode_comment_metatags(EpisodeComment $episodeComment): string
+if (! function_exists('set_episode_comment_metatags')) {
+    function set_episode_comment_metatags(EpisodeComment $episodeComment): void
     {
         $schema = new Schema(new Thing('SocialMediaPosting', [
             '@id' => url_to(
@@ -191,7 +190,7 @@ if (! function_exists('get_episode_comment_metatags')) {
                 $episodeComment->episode->slug,
                 $episodeComment->id
             ),
-            'datePublished' => $episodeComment->created_at->format(DATE_ISO8601),
+            'datePublished' => $episodeComment->created_at->format(DATE_ATOM),
             'author'        => new Thing('Person', [
                 'name' => $episodeComment->actor->display_name,
                 'url'  => $episodeComment->actor->uri,
@@ -200,8 +199,10 @@ if (! function_exists('get_episode_comment_metatags')) {
             'upvoteCount' => $episodeComment->likes_count,
         ]));
 
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+
+        $head
             ->title(lang('Comment.title', [
                 'actorDisplayName' => $episodeComment->actor->display_name,
                 'episodeTitle'     => $episodeComment->episode->title,
@@ -210,7 +211,7 @@ if (! function_exists('get_episode_comment_metatags')) {
             ->image($episodeComment->actor->avatar_image_url)
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')))
-            ->push('link', [
+            ->tag('link', null, [
                 'rel'  => 'alternate',
                 'type' => 'application/activity+json',
                 'href' => url_to(
@@ -219,17 +220,16 @@ if (! function_exists('get_episode_comment_metatags')) {
                     $episodeComment->episode->slug,
                     $episodeComment->id
                 ),
-            ]);
-
-        return $metatags->__toString() . PHP_EOL . $schema->__toString();
+            ])->appendRawContent((string) $schema);
     }
 }
 
-if (! function_exists('get_follow_metatags')) {
-    function get_follow_metatags(Actor $actor): string
+if (! function_exists('set_follow_metatags')) {
+    function set_follow_metatags(Actor $actor): void
     {
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+        $head
             ->title(lang('Podcast.followTitle', [
                 'actorDisplayName' => $actor->display_name,
             ]))
@@ -237,16 +237,15 @@ if (! function_exists('get_follow_metatags')) {
             ->image($actor->avatar_image_url)
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')));
-
-        return $metatags->__toString();
     }
 }
 
-if (! function_exists('get_remote_actions_metatags')) {
-    function get_remote_actions_metatags(Post $post, string $action): string
+if (! function_exists('set_remote_actions_metatags')) {
+    function set_remote_actions_metatags(Post $post, string $action): void
     {
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+        $head
             ->title(lang('Fediverse.' . $action . '.title', [
                 'actorDisplayName' => $post->actor->display_name,
             ],))
@@ -254,31 +253,30 @@ if (! function_exists('get_remote_actions_metatags')) {
             ->image($post->actor->avatar_image_url)
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')));
-
-        return $metatags->__toString();
     }
 }
 
-if (! function_exists('get_home_metatags')) {
-    function get_home_metatags(): string
+if (! function_exists('set_home_metatags')) {
+    function set_home_metatags(): void
     {
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+        $head
             ->title(service('settings')->get('App.siteName'))
             ->description(esc(service('settings')->get('App.siteDescription')))
             ->image(get_site_icon_url('512'))
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')));
 
-        return $metatags->__toString();
     }
 }
 
-if (! function_exists('get_page_metatags')) {
-    function get_page_metatags(Page $page): string
+if (! function_exists('set_page_metatags')) {
+    function set_page_metatags(Page $page): void
     {
-        $metatags = new MetaTags();
-        $metatags
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+        $head
             ->title(
                 $page->title . service('settings')->get('App.siteTitleSeparator') . service(
                     'settings'
@@ -289,7 +287,6 @@ if (! function_exists('get_page_metatags')) {
             ->canonical((string) current_url())
             ->og('site_name', esc(service('settings')->get('App.siteName')));
 
-        return $metatags->__toString();
     }
 }
 
diff --git a/app/Libraries/HtmlHead.php b/app/Libraries/HtmlHead.php
new file mode 100644
index 0000000000000000000000000000000000000000..99e64dc1ef8e0e880f2dccdec9f3dcd56502c812
--- /dev/null
+++ b/app/Libraries/HtmlHead.php
@@ -0,0 +1,188 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Libraries;
+
+use App\Controllers\WebmanifestController;
+use Override;
+use Stringable;
+
+/**
+ * Inspired by https://github.com/melbahja/seo
+ */
+class HtmlHead implements Stringable
+{
+    protected ?string $title = null;
+
+    /**
+     * @var array{name:string,value:string|null,attributes:array<string,string|null>}[]
+     */
+    protected array $tags = [];
+
+    protected string $rawContent = '';
+
+    #[Override]
+    public function __toString(): string
+    {
+        helper('misc');
+        $this
+            ->tag('meta', null, [
+                'charset' => 'UTF-8',
+            ])
+            ->meta('viewport', 'width=device-width, initial-scale=1.0')
+            ->tag('link', null, [
+                'rel'  => 'icon',
+                'type' => 'image/x-icon',
+                'href' => get_site_icon_url('ico'),
+            ])
+            ->tag('link', null, [
+                'rel'  => 'apple-touch-icon',
+                'href' => get_site_icon_url('180'),
+            ])
+            ->tag('link', null, [
+                'rel' => 'manifest',
+                // @phpstan-ignore-next-line
+                'href' => isset($podcast) ? route_to('podcast-webmanifest', esc($podcast->handle)) : route_to(
+                    'webmanifest'
+                ),
+            ])
+            ->meta(
+                'theme-color',
+                WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme']
+            )
+            ->tag('link', null, [
+                'rel'  => 'stylesheet',
+                'type' => 'text/css',
+                'href' => route_to('themes-colors-css'),
+            ])
+            ->appendRawContent(<<<HTML
+                <script>
+                // Check that service workers are supported
+                if ('serviceWorker' in navigator) {
+                    // Use the window load event to keep the page load performant
+                    window.addEventListener('load', () => {
+                        navigator.serviceWorker.register('/sw.js');
+                    });
+                }
+                </script>
+            HTML);
+
+        if ($this->title) {
+            $this->tag('title', esc($this->title));
+        }
+
+        if (url_is(route_to('admin') . '*') || url_is(base_url(config('Auth')->gateway) . '*')) {
+            // restricted admin and auth areas, do not index
+            $this->meta('robots', 'noindex');
+        } else {
+            // public website, set siteHead hook only there
+            service('plugins')
+                ->siteHead($this);
+        }
+
+        $head = '<head>';
+        foreach ($this->tags as $tag) {
+            if ($tag['value'] === null) {
+                $head .= <<<HTML
+                    <{$tag['name']}{$this->stringify_attributes($tag['attributes'])}/>
+                HTML;
+            } else {
+                $head .= <<<HTML
+                    <{$tag['name']} {$this->stringify_attributes($tag['attributes'])}>{$tag['value']}</{$tag['name']}>
+                HTML;
+            }
+        }
+
+        $head .= $this->rawContent . '</head>';
+
+        // reset head for next render
+        $this->title = null;
+        $this->tags = [];
+        $this->rawContent = '';
+
+        return $head;
+    }
+
+    public function title(string $title): self
+    {
+        $this->title = $title;
+        return $this->meta('title', $title)
+            ->og('title', $title)
+            ->twitter('title', $title);
+    }
+
+    public function description(string $desc): self
+    {
+        return $this->meta('description', $desc)
+            ->og('description', $desc)
+            ->twitter('description', $desc);
+    }
+
+    public function image(string $url, string $card = 'summary_large_image'): self
+    {
+        return $this->og('image', $url)
+            ->twitter('card', $card)
+            ->twitter('image', $url);
+    }
+
+    public function canonical(string $url): self
+    {
+        return $this->tag('link', null, [
+            'rel'  => 'canonical',
+            'href' => $url,
+        ]);
+    }
+
+    public function twitter(string $name, string $value): self
+    {
+        $this->meta("twitter:{$name}", $value);
+        return $this;
+    }
+
+    /**
+     * @param array<string,string|null> $attributes
+     */
+    public function tag(string $name, ?string $value = null, array $attributes = []): self
+    {
+        $this->tags[] = [
+            'name'       => $name,
+            'value'      => $value,
+            'attributes' => $attributes,
+        ];
+
+        return $this;
+    }
+
+    public function meta(string $name, string $content): self
+    {
+        $this->tag('meta', null, [
+            'name'    => $name,
+            'content' => $content,
+        ]);
+
+        return $this;
+    }
+
+    public function og(string $name, string $content): self
+    {
+        $this->meta('og:' . $name, $content);
+
+        return $this;
+    }
+
+    public function appendRawContent(string $content): self
+    {
+        $this->rawContent .= $content;
+
+        return $this;
+    }
+
+    /**
+     * @param array<string, string|null> $attributes
+     */
+    private function stringify_attributes(array $attributes): string
+    {
+        return stringify_attributes($attributes);
+    }
+}
diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php
index c999830bec736c3f80b75d269410281117d4b77c..f9954dc3276e5ed3bb87d56a87d987fa87a49952 100644
--- a/app/Models/EpisodeCommentModel.php
+++ b/app/Models/EpisodeCommentModel.php
@@ -229,8 +229,6 @@ class EpisodeCommentModel extends UuidModel
             $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
         );
 
-        // FIXME:?
-        // @phpstan-ignore-next-line
         return $this->convertUuidFieldsToStrings(
             $allEpisodeComments->getCustomResultObject($this->tempReturnType),
             $this->tempReturnType
diff --git a/app/Resources/icons/funding/_index.php b/app/Resources/icons/funding/_index.php
index e4e3444415530153f234fc0eac35ba8a3c43b8f7..f939dcca3b390ef644485dc0716c2ed7bf2d95ea 100644
--- a/app/Resources/icons/funding/_index.php
+++ b/app/Resources/icons/funding/_index.php
@@ -3,17 +3,17 @@
 declare(strict_types=1);
 
 /**
- * @icon('funding:buymeacoffee')
- * @icon('funding:donorbox')
- * @icon('funding:gofundme')
- * @icon('funding:helloasso')
- * @icon('funding:indiegogo')
- * @icon('funding:kickstarter')
- * @icon('funding:kisskissbankbank')
- * @icon('funding:kofi')
- * @icon('funding:liberapay')
- * @icon('funding:patreon')
- * @icon('funding:paypal')
- * @icon('funding:tipeee')
- * @icon('funding:ulule')
+ * @icon("funding:buymeacoffee")
+ * @icon("funding:donorbox")
+ * @icon("funding:gofundme")
+ * @icon("funding:helloasso")
+ * @icon("funding:indiegogo")
+ * @icon("funding:kickstarter")
+ * @icon("funding:kisskissbankbank")
+ * @icon("funding:kofi")
+ * @icon("funding:liberapay")
+ * @icon("funding:patreon")
+ * @icon("funding:paypal")
+ * @icon("funding:tipeee")
+ * @icon("funding:ulule")
  */
diff --git a/app/Resources/icons/podcasting/_index.php b/app/Resources/icons/podcasting/_index.php
index f6b7b176059dae283a5bdf50c167924aa55a1707..562854ecd9768799e7ea09a463917adfa1da1256 100644
--- a/app/Resources/icons/podcasting/_index.php
+++ b/app/Resources/icons/podcasting/_index.php
@@ -3,46 +3,46 @@
 declare(strict_types=1);
 
 /**
- * @icon('podcasting:amazon')
- * @icon('podcasting:antennapod')
- * @icon('podcasting:anytime')
- * @icon('podcasting:apple')
- * @icon('podcasting:blubrry')
- * @icon('podcasting:breez')
- * @icon('podcasting:castamatic')
- * @icon('podcasting:castbox')
- * @icon('podcasting:castopod')
- * @icon('podcasting:castro')
- * @icon('podcasting:deezer')
- * @icon('podcasting:episodes-fm')
- * @icon('podcasting:fountain')
- * @icon('podcasting:fyyd')
- * @icon('podcasting:gpodder')
- * @icon('podcasting:ivoox')
- * @icon('podcasting:listennotes')
- * @icon('podcasting:overcast')
- * @icon('podcasting:playerfm')
- * @icon('podcasting:plink')
- * @icon('podcasting:pocketcasts')
- * @icon('podcasting:podbean')
- * @icon('podcasting:podcastaddict')
- * @icon('podcasting:podcastguru')
- * @icon('podcasting:podcastindex')
- * @icon('podcasting:podchaser')
- * @icon('podcasting:podcloud')
- * @icon('podcasting:podfriend')
- * @icon('podcasting:podinstall')
- * @icon('podcasting:podlink')
- * @icon('podcasting:podlp')
- * @icon('podcasting:podnews')
- * @icon('podcasting:podtail')
- * @icon('podcasting:podverse')
- * @icon('podcasting:radiopublic')
- * @icon('podcasting:sphinxchat')
- * @icon('podcasting:spotify')
- * @icon('podcasting:spreaker')
- * @icon('podcasting:truefans')
- * @icon('podcasting:tsacdop')
- * @icon('podcasting:tunein')
- * @icon('podcasting:youtube-music')
+ * @icon("podcasting:amazon")
+ * @icon("podcasting:antennapod")
+ * @icon("podcasting:anytime")
+ * @icon("podcasting:apple")
+ * @icon("podcasting:blubrry")
+ * @icon("podcasting:breez")
+ * @icon("podcasting:castamatic")
+ * @icon("podcasting:castbox")
+ * @icon("podcasting:castopod")
+ * @icon("podcasting:castro")
+ * @icon("podcasting:deezer")
+ * @icon("podcasting:episodes-fm")
+ * @icon("podcasting:fountain")
+ * @icon("podcasting:fyyd")
+ * @icon("podcasting:gpodder")
+ * @icon("podcasting:ivoox")
+ * @icon("podcasting:listennotes")
+ * @icon("podcasting:overcast")
+ * @icon("podcasting:playerfm")
+ * @icon("podcasting:plink")
+ * @icon("podcasting:pocketcasts")
+ * @icon("podcasting:podbean")
+ * @icon("podcasting:podcastaddict")
+ * @icon("podcasting:podcastguru")
+ * @icon("podcasting:podcastindex")
+ * @icon("podcasting:podchaser")
+ * @icon("podcasting:podcloud")
+ * @icon("podcasting:podfriend")
+ * @icon("podcasting:podinstall")
+ * @icon("podcasting:podlink")
+ * @icon("podcasting:podlp")
+ * @icon("podcasting:podnews")
+ * @icon("podcasting:podtail")
+ * @icon("podcasting:podverse")
+ * @icon("podcasting:radiopublic")
+ * @icon("podcasting:sphinxchat")
+ * @icon("podcasting:spotify")
+ * @icon("podcasting:spreaker")
+ * @icon("podcasting:truefans")
+ * @icon("podcasting:tsacdop")
+ * @icon("podcasting:tunein")
+ * @icon("podcasting:youtube-music")
  */
diff --git a/app/Resources/icons/social/_index.php b/app/Resources/icons/social/_index.php
index 4e6a94ac1e7606c3fff1be83815dc49b38aded7a..571d8ab0aa6eaafe672e94b5efffe15a0484ec5c 100644
--- a/app/Resources/icons/social/_index.php
+++ b/app/Resources/icons/social/_index.php
@@ -3,26 +3,26 @@
 declare(strict_types=1);
 
 /**
- * @icon('social:bluesky')
- * @icon('social:discord')
- * @icon('social:facebook')
- * @icon('social:funkwhale')
- * @icon('social:instagram')
- * @icon('social:linkedin')
- * @icon('social:mastodon')
- * @icon('social:matrix')
- * @icon('social:misskey')
- * @icon('social:mobilizon')
- * @icon('social:peertube')
- * @icon('social:pixelfed')
- * @icon('social:pleroma')
- * @icon('social:plume')
- * @icon('social:slack')
- * @icon('social:telegram')
- * @icon('social:threads')
- * @icon('social:tiktok')
- * @icon('social:twitch')
- * @icon('social:writefreely')
- * @icon('social:x')
- * @icon('social:youtube')
+ * @icon("social:bluesky")
+ * @icon("social:discord")
+ * @icon("social:facebook")
+ * @icon("social:funkwhale")
+ * @icon("social:instagram")
+ * @icon("social:linkedin")
+ * @icon("social:mastodon")
+ * @icon("social:matrix")
+ * @icon("social:misskey")
+ * @icon("social:mobilizon")
+ * @icon("social:peertube")
+ * @icon("social:pixelfed")
+ * @icon("social:pleroma")
+ * @icon("social:plume")
+ * @icon("social:slack")
+ * @icon("social:telegram")
+ * @icon("social:threads")
+ * @icon("social:tiktok")
+ * @icon("social:twitch")
+ * @icon("social:writefreely")
+ * @icon("social:x")
+ * @icon("social:youtube")
  */
diff --git a/app/Resources/js/admin.ts b/app/Resources/js/admin.ts
index 6fb7ad57f5cb42208fb4d2b58cfa11ba10eccf5e..abef7b9afda0b431f13b9702885568fd864528b4 100644
--- a/app/Resources/js/admin.ts
+++ b/app/Resources/js/admin.ts
@@ -21,7 +21,7 @@ import Tooltip from "./modules/Tooltip";
 import ValidateFileSize from "./modules/ValidateFileSize";
 import "./modules/video-clip-previewer";
 import VideoClipBuilder from "./modules/VideoClipBuilder";
-import "./modules/xml-editor";
+import "./modules/code-editor";
 import "@patternfly/elements/pf-tabs/pf-tabs.js";
 import FieldArray from "./modules/FieldArray";
 
diff --git a/app/Resources/js/modules/code-editor.ts b/app/Resources/js/modules/code-editor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a8965865fa93630b9c8e891714b1c0d5572f916
--- /dev/null
+++ b/app/Resources/js/modules/code-editor.ts
@@ -0,0 +1,222 @@
+import { indentWithTab } from "@codemirror/commands";
+import { html as htmlLang } from "@codemirror/lang-html";
+import { xml } from "@codemirror/lang-xml";
+import {
+  defaultHighlightStyle,
+  syntaxHighlighting,
+} from "@codemirror/language";
+import { Compartment, EditorState, Extension } from "@codemirror/state";
+import { keymap, ViewUpdate } from "@codemirror/view";
+import { basicSetup, EditorView } from "codemirror";
+import { prettify as prettifyHTML, minify as minifyHTML } from "htmlfy";
+import { css, html, LitElement, TemplateResult } from "lit";
+import {
+  customElement,
+  property,
+  queryAssignedNodes,
+  state,
+} from "lit/decorators.js";
+import xmlFormat from "xml-formatter";
+
+const language = new Compartment();
+
+@customElement("code-editor")
+export class XMLEditor extends LitElement {
+  @queryAssignedNodes({ slot: "textarea" })
+  _textarea!: NodeListOf<HTMLTextAreaElement>;
+
+  @property()
+  lang = "html";
+
+  @state()
+  editorState!: EditorState;
+
+  @state()
+  editorView!: EditorView;
+
+  _textareaEvents = [
+    {
+      events: ["focus", "invalid"],
+      onEvent: (_: Event, editor: EditorView) => {
+        // focus editor when textarea is focused or invalid
+        editor.focus();
+      },
+    },
+  ];
+
+  firstUpdated(): void {
+    const minHeightEditor = EditorView.baseTheme({
+      ".cm-content, .cm-gutter": {
+        minHeight: this._textarea[0].clientHeight + "px",
+      },
+    });
+
+    const extensions: Extension[] = [
+      basicSetup,
+      keymap.of([indentWithTab]),
+      minHeightEditor,
+      syntaxHighlighting(defaultHighlightStyle),
+      EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
+        if (viewUpdate.docChanged) {
+          // Document changed, minify and update textarea value
+          switch (this.lang) {
+            case "xml":
+              this._textarea[0].value = minifyXML(
+                viewUpdate.state.doc.toString()
+              );
+              break;
+            case "html":
+              this._textarea[0].value = minifyHTML(
+                viewUpdate.state.doc.toString()
+              );
+              break;
+            default:
+              this._textarea[0].value = viewUpdate.state.doc.toString();
+              break;
+          }
+        }
+      }),
+    ];
+
+    let editorContents = "";
+    switch (this.lang) {
+      case "xml":
+        editorContents = formatXML(this._textarea[0].value);
+        extensions.push(language.of(xml()));
+        break;
+      case "html":
+        editorContents = prettifyHTML(this._textarea[0].value);
+        extensions.push(language.of(htmlLang()));
+        break;
+      default:
+        break;
+    }
+
+    this.editorState = EditorState.create({
+      doc: editorContents,
+      extensions: extensions,
+    });
+
+    this.editorView = new EditorView({
+      state: this.editorState,
+      root: this.shadowRoot as ShadowRoot,
+      parent: this.shadowRoot as ShadowRoot,
+    });
+
+    // hide textarea
+    this._textarea[0].style.position = "absolute";
+    this._textarea[0].style.opacity = "0";
+    this._textarea[0].style.zIndex = "-9999";
+    this._textarea[0].style.pointerEvents = "none";
+
+    for (const event of this._textareaEvents) {
+      event.events.forEach((name) => {
+        this._textarea[0].addEventListener(name, (e) =>
+          event.onEvent(e, this.editorView)
+        );
+      });
+    }
+  }
+
+  disconnectedCallback(): void {
+    super.disconnectedCallback();
+
+    for (const event of this._textareaEvents) {
+      event.events.forEach((name) => {
+        this._textarea[0].removeEventListener(name, (e) =>
+          event.onEvent(e, this.editorView)
+        );
+      });
+    }
+  }
+
+  static styles = css`
+    .cm-editor {
+      border-radius: 0.5rem;
+      overflow: hidden;
+      border: 3px solid hsl(var(--color-border-contrast));
+      background-color: hsl(var(--color-background-elevated));
+    }
+    .cm-editor.cm-focused {
+      outline: 2px solid transparent;
+      box-shadow:
+        0 0 0 2px hsl(var(--color-background-elevated)),
+        0 0 0 calc(4px) hsl(var(--color-accent-base));
+    }
+    .cm-gutters {
+      background-color: hsl(var(--color-background-elevated)) !important;
+    }
+
+    .cm-activeLine {
+      background-color: hsla(
+        var(--color-background-highlight) / 0.25
+      ) !important;
+    }
+
+    .cm-activeLineGutter {
+      background-color: hsl(var(--color-background-highlight)) !important;
+    }
+
+    .ͼ4 .cm-line {
+      caret-color: hsl(var(--color-text-base)) !important;
+    }
+
+    .ͼ1 .cm-cursor {
+      border: none;
+    }
+  `;
+
+  render(): TemplateResult<1> {
+    return html`<slot name="textarea"></slot>`;
+  }
+}
+
+function formatXML(contents: string) {
+  if (contents === "") {
+    return contents;
+  }
+
+  let editorContents = "";
+  try {
+    editorContents = xmlFormat(contents, {
+      indentation: "  ",
+    });
+  } catch {
+    // xml doesn't have a root node
+    editorContents = xmlFormat("<root>" + contents + "</root>", {
+      indentation: "  ",
+    });
+    // remove root, unnecessary lines and indents
+    editorContents = editorContents
+      .replace(/^<root>/, "")
+      .replace(/<\/root>$/, "")
+      .replace(/^\s*[\r\n]/gm, "")
+      .replace(/[\r\n] {2}/gm, "\r\n")
+      .trim();
+  }
+
+  return editorContents;
+}
+
+function minifyXML(contents: string) {
+  if (contents === "") {
+    return contents;
+  }
+
+  let minifiedContent = "";
+  try {
+    minifiedContent = xmlFormat.minify(contents, {
+      collapseContent: true,
+    });
+  } catch {
+    minifiedContent = xmlFormat.minify(`<root>${contents}</root>`, {
+      collapseContent: true,
+    });
+    // remove root
+    minifiedContent = minifiedContent
+      .replace(/^<root>/, "")
+      .replace(/<\/root>$/, "");
+  }
+
+  return minifiedContent;
+}
diff --git a/app/Resources/js/modules/xml-editor.ts b/app/Resources/js/modules/xml-editor.ts
deleted file mode 100644
index 8f57a1b226259905beade91c8d7030677c551d3a..0000000000000000000000000000000000000000
--- a/app/Resources/js/modules/xml-editor.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { indentWithTab } from "@codemirror/commands";
-import { xml } from "@codemirror/lang-xml";
-import {
-  defaultHighlightStyle,
-  syntaxHighlighting,
-} from "@codemirror/language";
-import { Compartment, EditorState } from "@codemirror/state";
-import { keymap, ViewUpdate } from "@codemirror/view";
-import { basicSetup, EditorView } from "codemirror";
-import { css, html, LitElement, TemplateResult } from "lit";
-import { customElement, queryAssignedNodes, state } from "lit/decorators.js";
-import prettifyXML from "xml-formatter";
-
-const language = new Compartment();
-
-@customElement("xml-editor")
-export class XMLEditor extends LitElement {
-  @queryAssignedNodes({ slot: "textarea" })
-  _textarea!: NodeListOf<HTMLTextAreaElement>;
-
-  @state()
-  editorState!: EditorState;
-
-  @state()
-  editorView!: EditorView;
-
-  firstUpdated(): void {
-    const minHeightEditor = EditorView.theme({
-      ".cm-content, .cm-gutter": {
-        minHeight: this._textarea[0].clientHeight + "px",
-      },
-    });
-
-    let editorContents = "";
-    if (this._textarea[0].value) {
-      try {
-        editorContents = prettifyXML(this._textarea[0].value, {
-          indentation: "  ",
-        });
-      } catch {
-        // xml doesn't have a root node
-        editorContents = prettifyXML(
-          "<root>" + this._textarea[0].value + "</root>",
-          {
-            indentation: "  ",
-          }
-        );
-        // remove root, unnecessary lines and indents
-        editorContents = editorContents
-          .replace(/^<root>/, "")
-          .replace(/<\/root>$/, "")
-          .replace(/^\s*[\r\n]/gm, "")
-          .replace(/[\r\n] {2}/gm, "\r\n")
-          .trim();
-      }
-    }
-
-    this.editorState = EditorState.create({
-      doc: editorContents,
-      extensions: [
-        basicSetup,
-        keymap.of([indentWithTab]),
-        language.of(xml()),
-        minHeightEditor,
-        syntaxHighlighting(defaultHighlightStyle),
-        EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
-          if (viewUpdate.docChanged) {
-            // Document changed, update textarea value
-            this._textarea[0].value = viewUpdate.state.doc.toString();
-          }
-        }),
-      ],
-    });
-
-    this.editorView = new EditorView({
-      state: this.editorState,
-      root: this.shadowRoot as ShadowRoot,
-      parent: this.shadowRoot as ShadowRoot,
-    });
-
-    // hide textarea
-    this._textarea[0].style.position = "absolute";
-    this._textarea[0].style.opacity = "0";
-    this._textarea[0].style.zIndex = "-9999";
-    this._textarea[0].style.pointerEvents = "none";
-  }
-
-  static styles = css`
-    .cm-editor {
-      border-radius: 0.5rem;
-      overflow: hidden;
-      border: 3px solid hsl(var(--color-border-contrast));
-      background-color: hsl(var(--color-background-elevated));
-    }
-    .cm-editor.cm-focused {
-      outline: 2px solid transparent;
-      box-shadow:
-        0 0 0 2px hsl(var(--color-background-elevated)),
-        0 0 0 calc(4px) hsl(var(--color-accent-base));
-    }
-    .cm-gutters {
-      background-color: hsl(var(--color-background-elevated)) !important;
-    }
-
-    .cm-activeLine {
-      background-color: hsl(var(--color-background-highlight)) !important;
-    }
-
-    .cm-activeLineGutter {
-      background-color: hsl(var(--color-background-highlight)) !important;
-    }
-
-    .ͼ4 .cm-line {
-      caret-color: hsl(var(--color-text-base)) !important;
-    }
-
-    .ͼ1 .cm-cursor {
-      border: none;
-    }
-  `;
-
-  render(): TemplateResult<1> {
-    return html`<slot name="textarea"></slot>`;
-  }
-}
diff --git a/app/Views/Components/Alert.php b/app/Views/Components/Alert.php
index a458787362e42d8f0e3585637ec2bf0abe38a55b..dcfbbd099249bb3036c5ca6b9af4c4a01c90c701 100644
--- a/app/Views/Components/Alert.php
+++ b/app/Views/Components/Alert.php
@@ -30,19 +30,19 @@ class Alert extends Component
         $variantData = match ($this->variant) {
             'success' => [
                 'class' => 'text-pine-900 bg-pine-100 border-pine-300',
-                'glyph' => 'check-fill', // @icon('check-fill')
+                'glyph' => 'check-fill', // @icon("check-fill")
             ],
             'danger' => [
                 'class' => 'text-red-900 bg-red-100 border-red-300',
-                'glyph' => 'close-fill', // @icon('close-fill')
+                'glyph' => 'close-fill', // @icon("close-fill")
             ],
             'warning' => [
                 'class' => 'text-yellow-900 bg-yellow-100 border-yellow-300',
-                'glyph' => 'alert-fill', // @icon('alert-fill')
+                'glyph' => 'alert-fill', // @icon("alert-fill")
             ],
             default => [
                 'class' => 'text-blue-900 bg-blue-100 border-blue-300',
-                'glyph' => 'error-warning-fill', // @icon('error-warning-fill')
+                'glyph' => 'error-warning-fill', // @icon("error-warning-fill")
             ],
         };
 
diff --git a/app/Views/Components/Forms/XMLEditor.php b/app/Views/Components/Forms/CodeEditor.php
similarity index 73%
rename from app/Views/Components/Forms/XMLEditor.php
rename to app/Views/Components/Forms/CodeEditor.php
index 4960378b77ee481458b8bc67eceb25efc12483ab..6c5d8c568c80cb0a475bc5f1740374f536d53639 100644
--- a/app/Views/Components/Forms/XMLEditor.php
+++ b/app/Views/Components/Forms/CodeEditor.php
@@ -6,20 +6,22 @@ namespace App\Views\Components\Forms;
 
 use Override;
 
-class XMLEditor extends FormComponent
+class CodeEditor extends FormComponent
 {
-    protected array $props = ['content'];
+    protected array $props = ['content', 'lang'];
 
     /**
      * @var array<string, string>
      */
     protected array $attributes = [
-        'rows'  => '5',
+        'rows'  => '6',
         'class' => 'textarea',
     ];
 
     protected string $content = '';
 
+    protected string $lang = '';
+
     public function setContent(string $value): void
     {
         $this->content = htmlspecialchars_decode($value);
@@ -32,7 +34,7 @@ class XMLEditor extends FormComponent
         $textarea = form_textarea($this->attributes, $this->content);
 
         return <<<HTML
-            <xml-editor>{$textarea}</xml-editor>
+            <code-editor lang="{$this->lang}">{$textarea}</code-editor>
         HTML;
     }
 }
diff --git a/app/Views/Decorators/SiteHead.php b/app/Views/Decorators/SiteHead.php
deleted file mode 100644
index b1c29123c7b5b1088801e37acb498746e1d31249..0000000000000000000000000000000000000000
--- a/app/Views/Decorators/SiteHead.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Views\Decorators;
-
-use CodeIgniter\View\ViewDecoratorInterface;
-use Override;
-
-class SiteHead implements ViewDecoratorInterface
-{
-    private static int $renderedCount = 0;
-
-    #[Override]
-    public static function decorate(string $html): string
-    {
-        if (url_is(config('Admin')->gateway . '*') || url_is(config('Install')->gateway)) {
-            return $html;
-        }
-
-        if (static::$renderedCount > 0) {
-            return $html;
-        }
-
-        ob_start(); // Start output buffering
-        // run hook to add tags to <head>
-        service('plugins')->siteHead();
-        $metaTags = ob_get_contents(); // Store buffer in variable
-        ob_end_clean();
-
-        if (str_contains($html, '</head>')) {
-            $html = str_replace('</head>', "\n\t{$metaTags}\n</head>", $html);
-            ++static::$renderedCount;
-        }
-
-        return $html;
-    }
-}
diff --git a/composer.json b/composer.json
index 3d82a097e291562cef29472f22825c09a9140971..136f9004548aa47556d4477bb98a250763483e99 100644
--- a/composer.json
+++ b/composer.json
@@ -9,37 +9,37 @@
     "php": "^8.3",
     "adaures/ipcat-php": "^v1.0.0",
     "adaures/podcast-persons-taxonomy": "^v1.0.1",
-    "aws/aws-sdk-php": "^3.325.2",
+    "aws/aws-sdk-php": "^3.334.7",
     "chrisjean/php-ico": "^1.0.4",
     "cocur/slugify": "^v4.6.0",
     "codeigniter4/framework": "v4.5.5",
     "codeigniter4/settings": "v2.2.0",
     "codeigniter4/shield": "v1.1.0",
     "codeigniter4/tasks": "dev-develop",
-    "geoip2/geoip2": "v3.0.0",
+    "geoip2/geoip2": "v3.1.0",
     "james-heinrich/getid3": "^2.0.0-beta6",
-    "league/commonmark": "^2.5.3",
+    "league/commonmark": "^2.6.0",
     "league/html-to-markdown": "5.1.1",
     "melbahja/seo": "^v2.1.1",
     "michalsn/codeigniter4-uuid": "v1.1.0",
     "mpratt/embera": "^2.0.41",
     "opawg/user-agents-v2-php": "dev-main",
-    "phpseclib/phpseclib": "~2.0.47",
+    "phpseclib/phpseclib": "~2.0.48",
     "vlucas/phpdotenv": "v5.6.1",
     "whichbrowser/parser": "^v2.1.8",
     "yassinedoghri/php-icons": "^v1.2.0",
     "yassinedoghri/podcast-feed": "dev-main"
   },
   "require-dev": {
-    "captainhook/captainhook": "^5.23.6",
-    "codeigniter/phpstan-codeigniter": "v1.4.3",
+    "captainhook/captainhook": "^5.24.1",
+    "codeigniter/phpstan-codeigniter": "v1.5.1",
     "mikey179/vfsstream": "^v1.6.12",
     "phpstan/extension-installer": "^1.4.3",
-    "phpstan/phpstan": "^1.12.7",
-    "phpunit/phpunit": "^10.5.38",
-    "rector/rector": "^1.2.9",
+    "phpstan/phpstan": "^2.0.3",
+    "phpunit/phpunit": "^11.5.1",
+    "rector/rector": "^2.0.3",
     "symplify/coding-standard": "^12.2.3",
-    "symplify/easy-coding-standard": "^12.3.6"
+    "symplify/easy-coding-standard": "^12.5.4"
   },
   "autoload": {
     "psr-4": {
diff --git a/composer.lock b/composer.lock
index 000e16290686e19b014a4dc853140944ee9561d2..3241e92459a4b0ee93477f9beca06531571a8c4f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
     "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
     "This file is @generated automatically"
   ],
-  "content-hash": "580247d059f13d464f012b442c0c8d5d",
+  "content-hash": "8bdf144f383531caa188f1c211ec092a",
   "packages": [
     {
       "name": "adaures/ipcat-php",
@@ -75,16 +75,16 @@
     },
     {
       "name": "adhocore/cli",
-      "version": "v1.7.2",
+      "version": "v1.9.3",
       "source": {
         "type": "git",
         "url": "https://github.com/adhocore/php-cli.git",
-        "reference": "57834cbaa4fb68cda849417ab86577fba2b15298"
+        "reference": "86be16e3c3b42d76fcdb32529bcded0fedb925d3"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/adhocore/php-cli/zipball/57834cbaa4fb68cda849417ab86577fba2b15298",
-        "reference": "57834cbaa4fb68cda849417ab86577fba2b15298",
+        "url": "https://api.github.com/repos/adhocore/php-cli/zipball/86be16e3c3b42d76fcdb32529bcded0fedb925d3",
+        "reference": "86be16e3c3b42d76fcdb32529bcded0fedb925d3",
         "shasum": ""
       },
       "require": {
@@ -95,6 +95,7 @@
       },
       "type": "library",
       "autoload": {
+        "files": ["src/functions.php"],
         "psr-4": {
           "Ahc\\Cli\\": "src/"
         }
@@ -127,7 +128,7 @@
       ],
       "support": {
         "issues": "https://github.com/adhocore/php-cli/issues",
-        "source": "https://github.com/adhocore/php-cli/tree/v1.7.2"
+        "source": "https://github.com/adhocore/php-cli/tree/v1.9.3"
       },
       "funding": [
         {
@@ -139,7 +140,7 @@
           "type": "github"
         }
       ],
-      "time": "2024-09-05T00:08:47+00:00"
+      "time": "2024-12-04T03:40:29+00:00"
     },
     {
       "name": "aws/aws-crt-php",
@@ -188,16 +189,16 @@
     },
     {
       "name": "aws/aws-sdk-php",
-      "version": "3.325.2",
+      "version": "3.334.7",
       "source": {
         "type": "git",
         "url": "https://github.com/aws/aws-sdk-php.git",
-        "reference": "9e354a5e0cd1d563ec85245e3000e98e16a44fce"
+        "reference": "8e0104e95a1edba209e077e6c4212b8cca04686f"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9e354a5e0cd1d563ec85245e3000e98e16a44fce",
-        "reference": "9e354a5e0cd1d563ec85245e3000e98e16a44fce",
+        "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8e0104e95a1edba209e077e6c4212b8cca04686f",
+        "reference": "8e0104e95a1edba209e077e6c4212b8cca04686f",
         "shasum": ""
       },
       "require": {
@@ -226,8 +227,8 @@
         "nette/neon": "^2.3",
         "paragonie/random_compat": ">= 2",
         "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
-        "psr/cache": "^1.0",
-        "psr/simple-cache": "^1.0",
+        "psr/cache": "^1.0 || ^2.0 || ^3.0",
+        "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
         "sebastian/comparator": "^1.2.3 || ^4.0",
         "yoast/phpunit-polyfills": "^1.0"
       },
@@ -274,9 +275,9 @@
       "support": {
         "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
         "issues": "https://github.com/aws/aws-sdk-php/issues",
-        "source": "https://github.com/aws/aws-sdk-php/tree/3.325.2"
+        "source": "https://github.com/aws/aws-sdk-php/tree/3.334.7"
       },
-      "time": "2024-11-01T18:08:38+00:00"
+      "time": "2024-12-16T19:09:36+00:00"
     },
     {
       "name": "brick/math",
@@ -638,12 +639,12 @@
       "source": {
         "type": "git",
         "url": "https://github.com/codeigniter4/tasks.git",
-        "reference": "8767036adfef764f0623ba9cde2998320b948b3b"
+        "reference": "ec717c428c6f0e8e216e5dfbf18966a26a4f821f"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/codeigniter4/tasks/zipball/8767036adfef764f0623ba9cde2998320b948b3b",
-        "reference": "8767036adfef764f0623ba9cde2998320b948b3b",
+        "url": "https://api.github.com/repos/codeigniter4/tasks/zipball/ec717c428c6f0e8e216e5dfbf18966a26a4f821f",
+        "reference": "ec717c428c6f0e8e216e5dfbf18966a26a4f821f",
         "shasum": ""
       },
       "require": {
@@ -656,7 +657,7 @@
         "codeigniter4/devkit": "^1.0",
         "codeigniter4/framework": "^4.1",
         "phpunit/phpunit": "^9.6",
-        "rector/rector": "1.2.8"
+        "rector/rector": "1.2.10"
       },
       "default-branch": true,
       "type": "library",
@@ -714,20 +715,20 @@
         "source": "https://github.com/codeigniter4/tasks/tree/develop",
         "issues": "https://github.com/codeigniter4/tasks/issues"
       },
-      "time": "2024-10-21T18:02:22+00:00"
+      "time": "2024-11-11T12:05:47+00:00"
     },
     {
       "name": "composer/ca-bundle",
-      "version": "1.5.3",
+      "version": "1.5.4",
       "source": {
         "type": "git",
         "url": "https://github.com/composer/ca-bundle.git",
-        "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2"
+        "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2",
-        "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2",
+        "url": "https://api.github.com/repos/composer/ca-bundle/zipball/bc0593537a463e55cadf45fd938d23b75095b7e1",
+        "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1",
         "shasum": ""
       },
       "require": {
@@ -766,7 +767,7 @@
       "support": {
         "irc": "irc://irc.freenode.org/composer",
         "issues": "https://github.com/composer/ca-bundle/issues",
-        "source": "https://github.com/composer/ca-bundle/tree/1.5.3"
+        "source": "https://github.com/composer/ca-bundle/tree/1.5.4"
       },
       "funding": [
         {
@@ -782,7 +783,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-11-04T10:15:26+00:00"
+      "time": "2024-11-27T15:35:25+00:00"
     },
     {
       "name": "dflydev/dot-access-data",
@@ -854,22 +855,22 @@
     },
     {
       "name": "geoip2/geoip2",
-      "version": "v3.0.0",
+      "version": "v3.1.0",
       "source": {
         "type": "git",
         "url": "https://github.com/maxmind/GeoIP2-php.git",
-        "reference": "1a802ce9356cdd1c6b681c030fd9563750e11e6a"
+        "reference": "c86fbeaa7e42279dd9e7af0b015384e721832b88"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/1a802ce9356cdd1c6b681c030fd9563750e11e6a",
-        "reference": "1a802ce9356cdd1c6b681c030fd9563750e11e6a",
+        "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/c86fbeaa7e42279dd9e7af0b015384e721832b88",
+        "reference": "c86fbeaa7e42279dd9e7af0b015384e721832b88",
         "shasum": ""
       },
       "require": {
         "ext-json": "*",
-        "maxmind-db/reader": "^1.11.1",
-        "maxmind/web-service-common": "~0.8",
+        "maxmind-db/reader": "^1.12.0",
+        "maxmind/web-service-common": "~0.10",
         "php": ">=8.1"
       },
       "require-dev": {
@@ -898,9 +899,9 @@
       "keywords": ["IP", "geoip", "geoip2", "geolocation", "maxmind"],
       "support": {
         "issues": "https://github.com/maxmind/GeoIP2-php/issues",
-        "source": "https://github.com/maxmind/GeoIP2-php/tree/v3.0.0"
+        "source": "https://github.com/maxmind/GeoIP2-php/tree/v3.1.0"
       },
-      "time": "2023-12-04T17:16:34+00:00"
+      "time": "2024-11-15T16:33:31+00:00"
     },
     {
       "name": "graham-campbell/result-type",
@@ -1410,16 +1411,16 @@
     },
     {
       "name": "league/commonmark",
-      "version": "2.5.3",
+      "version": "2.6.0",
       "source": {
         "type": "git",
         "url": "https://github.com/thephpleague/commonmark.git",
-        "reference": "b650144166dfa7703e62a22e493b853b58d874b0"
+        "reference": "d150f911e0079e90ae3c106734c93137c184f932"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0",
-        "reference": "b650144166dfa7703e62a22e493b853b58d874b0",
+        "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932",
+        "reference": "d150f911e0079e90ae3c106734c93137c184f932",
         "shasum": ""
       },
       "require": {
@@ -1444,8 +1445,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/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0",
+        "symfony/finder": "^5.3 | ^6.0 | ^7.0",
+        "symfony/process": "^5.4 | ^6.0 | ^7.0",
+        "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
         "unleashedtech/php-coding-standard": "^3.1.1",
         "vimeo/psalm": "^4.24.0 || ^5.0.0"
       },
@@ -1455,7 +1457,7 @@
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "2.6-dev"
+          "dev-main": "2.7-dev"
         }
       },
       "autoload": {
@@ -1510,7 +1512,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-08-16T11:46:16+00:00"
+      "time": "2024-12-07T15:34:16+00:00"
     },
     {
       "name": "league/config",
@@ -1676,29 +1678,27 @@
     },
     {
       "name": "maxmind-db/reader",
-      "version": "v1.11.1",
+      "version": "v1.12.0",
       "source": {
         "type": "git",
         "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
-        "reference": "1e66f73ffcf25e17c7a910a1317e9720a95497c7"
+        "reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/1e66f73ffcf25e17c7a910a1317e9720a95497c7",
-        "reference": "1e66f73ffcf25e17c7a910a1317e9720a95497c7",
+        "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
+        "reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
         "shasum": ""
       },
       "require": {
         "php": ">=7.2"
       },
       "conflict": {
-        "ext-maxminddb": "<1.11.1,>=2.0.0"
+        "ext-maxminddb": "<1.11.1 || >=2.0.0"
       },
       "require-dev": {
         "friendsofphp/php-cs-fixer": "3.*",
-        "php-coveralls/php-coveralls": "^2.1",
         "phpstan/phpstan": "*",
-        "phpunit/phpcov": ">=6.0.0",
         "phpunit/phpunit": ">=8.0.0,<10.0.0",
         "squizlabs/php_codesniffer": "3.*"
       },
@@ -1727,29 +1727,29 @@
       "keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"],
       "support": {
         "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
-        "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.11.1"
+        "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.12.0"
       },
-      "time": "2023-12-02T00:09:23+00:00"
+      "time": "2024-11-14T22:43:47+00:00"
     },
     {
       "name": "maxmind/web-service-common",
-      "version": "v0.9.0",
+      "version": "v0.10.0",
       "source": {
         "type": "git",
         "url": "https://github.com/maxmind/web-service-common-php.git",
-        "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53"
+        "reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/4dc5a3e8df38aea4ca3b1096cee3a038094e9b53",
-        "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53",
+        "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
+        "reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
         "shasum": ""
       },
       "require": {
         "composer/ca-bundle": "^1.0.3",
         "ext-curl": "*",
         "ext-json": "*",
-        "php": ">=7.2"
+        "php": ">=8.1"
       },
       "require-dev": {
         "friendsofphp/php-cs-fixer": "3.*",
@@ -1776,9 +1776,9 @@
       "homepage": "https://github.com/maxmind/web-service-common-php",
       "support": {
         "issues": "https://github.com/maxmind/web-service-common-php/issues",
-        "source": "https://github.com/maxmind/web-service-common-php/tree/v0.9.0"
+        "source": "https://github.com/maxmind/web-service-common-php/tree/v0.10.0"
       },
-      "time": "2022-03-28T17:43:20+00:00"
+      "time": "2024-11-14T23:14:52+00:00"
     },
     {
       "name": "melbahja/seo",
@@ -2250,16 +2250,16 @@
     },
     {
       "name": "phpseclib/phpseclib",
-      "version": "2.0.47",
+      "version": "2.0.48",
       "source": {
         "type": "git",
         "url": "https://github.com/phpseclib/phpseclib.git",
-        "reference": "b7d7d90ee7df7f33a664b4aea32d50a305d35adb"
+        "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b7d7d90ee7df7f33a664b4aea32d50a305d35adb",
-        "reference": "b7d7d90ee7df7f33a664b4aea32d50a305d35adb",
+        "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/eaa7be704b8b93a6913b69eb7f645a59d7731b61",
+        "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61",
         "shasum": ""
       },
       "require": {
@@ -2336,7 +2336,7 @@
       ],
       "support": {
         "issues": "https://github.com/phpseclib/phpseclib/issues",
-        "source": "https://github.com/phpseclib/phpseclib/tree/2.0.47"
+        "source": "https://github.com/phpseclib/phpseclib/tree/2.0.48"
       },
       "funding": [
         {
@@ -2352,7 +2352,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-02-26T04:55:38+00:00"
+      "time": "2024-12-14T21:03:54+00:00"
     },
     {
       "name": "psr/cache",
@@ -2840,16 +2840,16 @@
     },
     {
       "name": "symfony/deprecation-contracts",
-      "version": "v3.5.0",
+      "version": "v3.5.1",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/deprecation-contracts.git",
-        "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
+        "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
-        "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+        "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+        "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
         "shasum": ""
       },
       "require": {
@@ -2883,7 +2883,7 @@
       "description": "A generic function and convention to trigger deprecation notices",
       "homepage": "https://symfony.com",
       "support": {
-        "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
+        "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
       },
       "funding": [
         {
@@ -2899,7 +2899,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-04-18T09:32:20+00:00"
+      "time": "2024-09-25T14:20:29+00:00"
     },
     {
       "name": "symfony/polyfill-ctype",
@@ -2927,8 +2927,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -2997,8 +2997,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -3061,8 +3061,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -3348,16 +3348,16 @@
   "packages-dev": [
     {
       "name": "captainhook/captainhook",
-      "version": "5.23.6",
+      "version": "5.24.1",
       "source": {
         "type": "git",
         "url": "https://github.com/captainhookphp/captainhook.git",
-        "reference": "6c9a60f5771581f3788f98d7b4aa9a61156cfebb"
+        "reference": "1e56452fd7a7e486e5955ab72dc9ea34bb52a184"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/captainhookphp/captainhook/zipball/6c9a60f5771581f3788f98d7b4aa9a61156cfebb",
-        "reference": "6c9a60f5771581f3788f98d7b4aa9a61156cfebb",
+        "url": "https://api.github.com/repos/captainhookphp/captainhook/zipball/1e56452fd7a7e486e5955ab72dc9ea34bb52a184",
+        "reference": "1e56452fd7a7e486e5955ab72dc9ea34bb52a184",
         "shasum": ""
       },
       "require": {
@@ -3416,7 +3416,7 @@
       ],
       "support": {
         "issues": "https://github.com/captainhookphp/captainhook/issues",
-        "source": "https://github.com/captainhookphp/captainhook/tree/5.23.6"
+        "source": "https://github.com/captainhookphp/captainhook/tree/5.24.1"
       },
       "funding": [
         {
@@ -3424,20 +3424,20 @@
           "type": "github"
         }
       ],
-      "time": "2024-10-30T14:15:58+00:00"
+      "time": "2024-11-26T18:42:37+00:00"
     },
     {
       "name": "captainhook/secrets",
-      "version": "0.9.5",
+      "version": "0.9.6",
       "source": {
         "type": "git",
         "url": "https://github.com/captainhookphp/secrets.git",
-        "reference": "8aa90d5b9b7892abd11b9da2fc172a7b32b90cbe"
+        "reference": "0232c67019e11c4bee4ee9bfec9575b67e0854e5"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/captainhookphp/secrets/zipball/8aa90d5b9b7892abd11b9da2fc172a7b32b90cbe",
-        "reference": "8aa90d5b9b7892abd11b9da2fc172a7b32b90cbe",
+        "url": "https://api.github.com/repos/captainhookphp/secrets/zipball/0232c67019e11c4bee4ee9bfec9575b67e0854e5",
+        "reference": "0232c67019e11c4bee4ee9bfec9575b67e0854e5",
         "shasum": ""
       },
       "require": {
@@ -3470,7 +3470,7 @@
       ],
       "support": {
         "issues": "https://github.com/captainhookphp/secrets/issues",
-        "source": "https://github.com/captainhookphp/secrets/tree/0.9.5"
+        "source": "https://github.com/captainhookphp/secrets/tree/0.9.6"
       },
       "funding": [
         {
@@ -3478,7 +3478,7 @@
           "type": "github"
         }
       ],
-      "time": "2023-11-30T18:10:18+00:00"
+      "time": "2024-11-26T09:24:19+00:00"
     },
     {
       "name": "clue/ndjson-react",
@@ -3544,38 +3544,36 @@
     },
     {
       "name": "codeigniter/phpstan-codeigniter",
-      "version": "v1.4.3",
+      "version": "v1.5.1",
       "source": {
         "type": "git",
         "url": "https://github.com/CodeIgniter/phpstan-codeigniter.git",
-        "reference": "bff4a7cfe251bb288223e95d6f588e956dfc0a93"
+        "reference": "4bfaba879007c7dfb9c3b687713bd5d45524f067"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/CodeIgniter/phpstan-codeigniter/zipball/bff4a7cfe251bb288223e95d6f588e956dfc0a93",
-        "reference": "bff4a7cfe251bb288223e95d6f588e956dfc0a93",
+        "url": "https://api.github.com/repos/CodeIgniter/phpstan-codeigniter/zipball/4bfaba879007c7dfb9c3b687713bd5d45524f067",
+        "reference": "4bfaba879007c7dfb9c3b687713bd5d45524f067",
         "shasum": ""
       },
       "require": {
-        "codeigniter4/framework": "^4.4",
+        "codeigniter4/framework": "^4.5",
         "php": "^8.1",
-        "phpstan/phpstan": "^1.10"
+        "phpstan/phpstan": "^2.0"
       },
       "conflict": {
         "codeigniter/framework": "*"
       },
       "require-dev": {
         "codeigniter/coding-standard": "^1.7",
-        "codeigniter4/shield": "^1.0@beta",
-        "friendsofphp/php-cs-fixer": "^3.20",
-        "nexusphp/cs-config": "^3.12",
-        "php-parallel-lint/php-parallel-lint": "^1.3",
+        "codeigniter4/shield": "^1.0",
+        "friendsofphp/php-cs-fixer": "^3.49",
+        "nexusphp/cs-config": "^3.21",
         "phpstan/extension-installer": "^1.3",
-        "phpstan/phpstan-deprecation-rules": "^1.1",
-        "phpstan/phpstan-phpunit": "^1.3",
-        "phpstan/phpstan-strict-rules": "^1.5",
-        "phpunit/phpunit": "^10.2",
-        "rector/rector": "^0.18.2"
+        "phpstan/phpstan-deprecation-rules": "^2.0",
+        "phpstan/phpstan-phpunit": "^2.0",
+        "phpstan/phpstan-strict-rules": "^2.0",
+        "phpunit/phpunit": "^10.5 || ^11.4"
       },
       "type": "phpstan-extension",
       "extra": {
@@ -3610,20 +3608,20 @@
         "slack": "https://codeigniterchat.slack.com",
         "source": "https://github.com/CodeIgniter/phpstan-codeigniter"
       },
-      "time": "2023-12-21T03:39:48+00:00"
+      "time": "2024-12-02T15:33:25+00:00"
     },
     {
       "name": "composer/pcre",
-      "version": "3.3.1",
+      "version": "3.3.2",
       "source": {
         "type": "git",
         "url": "https://github.com/composer/pcre.git",
-        "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4"
+        "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4",
-        "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4",
+        "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+        "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
         "shasum": ""
       },
       "require": {
@@ -3633,8 +3631,8 @@
         "phpstan/phpstan": "<1.11.10"
       },
       "require-dev": {
-        "phpstan/phpstan": "^1.11.10",
-        "phpstan/phpstan-strict-rules": "^1.1",
+        "phpstan/phpstan": "^1.12 || ^2",
+        "phpstan/phpstan-strict-rules": "^1 || ^2",
         "phpunit/phpunit": "^8 || ^9"
       },
       "type": "library",
@@ -3664,7 +3662,7 @@
       "keywords": ["PCRE", "preg", "regex", "regular expression"],
       "support": {
         "issues": "https://github.com/composer/pcre/issues",
-        "source": "https://github.com/composer/pcre/tree/3.3.1"
+        "source": "https://github.com/composer/pcre/tree/3.3.2"
       },
       "funding": [
         {
@@ -3680,7 +3678,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-08-27T18:44:43+00:00"
+      "time": "2024-11-12T16:29:46+00:00"
     },
     {
       "name": "composer/semver",
@@ -3917,16 +3915,16 @@
     },
     {
       "name": "friendsofphp/php-cs-fixer",
-      "version": "v3.64.0",
+      "version": "v3.65.0",
       "source": {
         "type": "git",
         "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
-        "reference": "58dd9c931c785a79739310aef5178928305ffa67"
+        "reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67",
-        "reference": "58dd9c931c785a79739310aef5178928305ffa67",
+        "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
+        "reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
         "shasum": ""
       },
       "require": {
@@ -3936,7 +3934,7 @@
         "ext-filter": "*",
         "ext-json": "*",
         "ext-tokenizer": "*",
-        "fidry/cpu-core-counter": "^1.0",
+        "fidry/cpu-core-counter": "^1.2",
         "php": "^7.4 || ^8.0",
         "react/child-process": "^0.6.5",
         "react/event-loop": "^1.0",
@@ -3956,18 +3954,18 @@
         "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0"
       },
       "require-dev": {
-        "facile-it/paraunit": "^1.3 || ^2.3",
-        "infection/infection": "^0.29.5",
-        "justinrainbow/json-schema": "^5.2",
+        "facile-it/paraunit": "^1.3.1 || ^2.4",
+        "infection/infection": "^0.29.8",
+        "justinrainbow/json-schema": "^5.3 || ^6.0",
         "keradus/cli-executor": "^2.1",
-        "mikey179/vfsstream": "^1.6.11",
+        "mikey179/vfsstream": "^1.6.12",
         "php-coveralls/php-coveralls": "^2.7",
         "php-cs-fixer/accessible-object": "^1.1",
         "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5",
         "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5",
-        "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2",
-        "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0",
-        "symfony/yaml": "^5.4 || ^6.0 || ^7.0"
+        "phpunit/phpunit": "^9.6.21 || ^10.5.38 || ^11.4.3",
+        "symfony/var-dumper": "^5.4.47 || ^6.4.15 || ^7.1.8",
+        "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.1.6"
       },
       "suggest": {
         "ext-dom": "For handling output formats in XML",
@@ -4002,7 +4000,7 @@
       ],
       "support": {
         "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
-        "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0"
+        "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.65.0"
       },
       "funding": [
         {
@@ -4010,7 +4008,7 @@
           "type": "github"
         }
       ],
-      "time": "2024-08-30T23:09:38+00:00"
+      "time": "2024-11-25T00:39:24+00:00"
     },
     {
       "name": "mikey179/vfsstream",
@@ -4064,16 +4062,16 @@
     },
     {
       "name": "myclabs/deep-copy",
-      "version": "1.12.0",
+      "version": "1.12.1",
       "source": {
         "type": "git",
         "url": "https://github.com/myclabs/DeepCopy.git",
-        "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
+        "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
-        "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+        "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
+        "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
         "shasum": ""
       },
       "require": {
@@ -4102,7 +4100,7 @@
       "keywords": ["clone", "copy", "duplicate", "object", "object graph"],
       "support": {
         "issues": "https://github.com/myclabs/DeepCopy/issues",
-        "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
+        "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
       },
       "funding": [
         {
@@ -4110,7 +4108,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-06-12T14:39:25+00:00"
+      "time": "2024-11-08T17:47:46+00:00"
     },
     {
       "name": "nikic/php-parser",
@@ -4318,20 +4316,20 @@
     },
     {
       "name": "phpstan/phpstan",
-      "version": "1.12.7",
+      "version": "2.0.3",
       "source": {
         "type": "git",
         "url": "https://github.com/phpstan/phpstan.git",
-        "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0"
+        "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
-        "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
+        "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46b4d3529b12178112d9008337beda0cc2a1a6b4",
+        "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4",
         "shasum": ""
       },
       "require": {
-        "php": "^7.2|^8.0"
+        "php": "^7.4|^8.0"
       },
       "conflict": {
         "phpstan/phpstan-shim": "*"
@@ -4362,39 +4360,39 @@
           "type": "github"
         }
       ],
-      "time": "2024-10-18T11:12:07+00:00"
+      "time": "2024-11-28T22:19:37+00:00"
     },
     {
       "name": "phpunit/php-code-coverage",
-      "version": "10.1.16",
+      "version": "11.0.8",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-        "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
+        "reference": "418c59fd080954f8c4aa5631d9502ecda2387118"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
-        "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
+        "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118",
+        "reference": "418c59fd080954f8c4aa5631d9502ecda2387118",
         "shasum": ""
       },
       "require": {
         "ext-dom": "*",
         "ext-libxml": "*",
         "ext-xmlwriter": "*",
-        "nikic/php-parser": "^4.19.1 || ^5.1.0",
-        "php": ">=8.1",
-        "phpunit/php-file-iterator": "^4.1.0",
-        "phpunit/php-text-template": "^3.0.1",
-        "sebastian/code-unit-reverse-lookup": "^3.0.0",
-        "sebastian/complexity": "^3.2.0",
-        "sebastian/environment": "^6.1.0",
-        "sebastian/lines-of-code": "^2.0.2",
-        "sebastian/version": "^4.0.1",
+        "nikic/php-parser": "^5.3.1",
+        "php": ">=8.2",
+        "phpunit/php-file-iterator": "^5.1.0",
+        "phpunit/php-text-template": "^4.0.1",
+        "sebastian/code-unit-reverse-lookup": "^4.0.1",
+        "sebastian/complexity": "^4.0.1",
+        "sebastian/environment": "^7.2.0",
+        "sebastian/lines-of-code": "^3.0.1",
+        "sebastian/version": "^5.0.2",
         "theseer/tokenizer": "^1.2.3"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.1"
+        "phpunit/phpunit": "^11.5.0"
       },
       "suggest": {
         "ext-pcov": "PHP extension that provides line coverage",
@@ -4403,7 +4401,7 @@
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "10.1.x-dev"
+          "dev-main": "11.0.x-dev"
         }
       },
       "autoload": {
@@ -4424,7 +4422,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/10.1.16"
+        "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8"
       },
       "funding": [
         {
@@ -4432,32 +4430,32 @@
           "type": "github"
         }
       ],
-      "time": "2024-08-22T04:31:57+00:00"
+      "time": "2024-12-11T12:34:27+00:00"
     },
     {
       "name": "phpunit/php-file-iterator",
-      "version": "4.1.0",
+      "version": "5.1.0",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-        "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
+        "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
-        "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
+        "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
+        "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "4.0-dev"
+          "dev-main": "5.0-dev"
         }
       },
       "autoload": {
@@ -4478,7 +4476,7 @@
       "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/4.1.0"
+        "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0"
       },
       "funding": [
         {
@@ -4486,28 +4484,28 @@
           "type": "github"
         }
       ],
-      "time": "2023-08-31T06:24:48+00:00"
+      "time": "2024-08-27T05:02:59+00:00"
     },
     {
       "name": "phpunit/php-invoker",
-      "version": "4.0.0",
+      "version": "5.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/php-invoker.git",
-        "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
+        "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
-        "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+        "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+        "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
         "ext-pcntl": "*",
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "suggest": {
         "ext-pcntl": "*"
@@ -4515,7 +4513,7 @@
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "4.0-dev"
+          "dev-main": "5.0-dev"
         }
       },
       "autoload": {
@@ -4535,7 +4533,8 @@
       "keywords": ["process"],
       "support": {
         "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
-        "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
+        "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+        "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
       },
       "funding": [
         {
@@ -4543,32 +4542,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T06:56:09+00:00"
+      "time": "2024-07-03T05:07:44+00:00"
     },
     {
       "name": "phpunit/php-text-template",
-      "version": "3.0.1",
+      "version": "4.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/php-text-template.git",
-        "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
+        "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
-        "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+        "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+        "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "3.0-dev"
+          "dev-main": "4.0-dev"
         }
       },
       "autoload": {
@@ -4589,7 +4588,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
         "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
-        "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
+        "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
       },
       "funding": [
         {
@@ -4597,32 +4596,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-08-31T14:07:24+00:00"
+      "time": "2024-07-03T05:08:43+00:00"
     },
     {
       "name": "phpunit/php-timer",
-      "version": "6.0.0",
+      "version": "7.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/php-timer.git",
-        "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
+        "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
-        "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+        "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+        "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "6.0-dev"
+          "dev-main": "7.0-dev"
         }
       },
       "autoload": {
@@ -4642,7 +4641,8 @@
       "keywords": ["timer"],
       "support": {
         "issues": "https://github.com/sebastianbergmann/php-timer/issues",
-        "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
+        "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+        "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
       },
       "funding": [
         {
@@ -4650,20 +4650,20 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T06:57:52+00:00"
+      "time": "2024-07-03T05:09:35+00:00"
     },
     {
       "name": "phpunit/phpunit",
-      "version": "10.5.38",
+      "version": "11.5.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/phpunit.git",
-        "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
+        "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
-        "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
+        "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a",
+        "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a",
         "shasum": ""
       },
       "require": {
@@ -4673,26 +4673,26 @@
         "ext-mbstring": "*",
         "ext-xml": "*",
         "ext-xmlwriter": "*",
-        "myclabs/deep-copy": "^1.12.0",
+        "myclabs/deep-copy": "^1.12.1",
         "phar-io/manifest": "^2.0.4",
         "phar-io/version": "^3.2.1",
-        "php": ">=8.1",
-        "phpunit/php-code-coverage": "^10.1.16",
-        "phpunit/php-file-iterator": "^4.1.0",
-        "phpunit/php-invoker": "^4.0.0",
-        "phpunit/php-text-template": "^3.0.1",
-        "phpunit/php-timer": "^6.0.0",
-        "sebastian/cli-parser": "^2.0.1",
-        "sebastian/code-unit": "^2.0.0",
-        "sebastian/comparator": "^5.0.3",
-        "sebastian/diff": "^5.1.1",
-        "sebastian/environment": "^6.1.0",
-        "sebastian/exporter": "^5.1.2",
-        "sebastian/global-state": "^6.0.2",
-        "sebastian/object-enumerator": "^5.0.0",
-        "sebastian/recursion-context": "^5.0.0",
-        "sebastian/type": "^4.0.0",
-        "sebastian/version": "^4.0.1"
+        "php": ">=8.2",
+        "phpunit/php-code-coverage": "^11.0.7",
+        "phpunit/php-file-iterator": "^5.1.0",
+        "phpunit/php-invoker": "^5.0.1",
+        "phpunit/php-text-template": "^4.0.1",
+        "phpunit/php-timer": "^7.0.1",
+        "sebastian/cli-parser": "^3.0.2",
+        "sebastian/code-unit": "^3.0.1",
+        "sebastian/comparator": "^6.2.1",
+        "sebastian/diff": "^6.0.2",
+        "sebastian/environment": "^7.2.0",
+        "sebastian/exporter": "^6.3.0",
+        "sebastian/global-state": "^7.0.2",
+        "sebastian/object-enumerator": "^6.0.1",
+        "sebastian/type": "^5.1.0",
+        "sebastian/version": "^5.0.2",
+        "staabm/side-effects-detector": "^1.0.5"
       },
       "suggest": {
         "ext-soap": "To be able to generate mocks based on WSDL files"
@@ -4701,7 +4701,7 @@
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "10.5-dev"
+          "dev-main": "11.5-dev"
         }
       },
       "autoload": {
@@ -4723,7 +4723,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/phpunit/issues",
         "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-        "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
+        "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1"
       },
       "funding": [
         {
@@ -4739,7 +4739,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-10-28T13:06:21+00:00"
+      "time": "2024-12-11T10:52:48+00:00"
     },
     {
       "name": "psr/container",
@@ -5282,21 +5282,21 @@
     },
     {
       "name": "rector/rector",
-      "version": "1.2.9",
+      "version": "2.0.3",
       "source": {
         "type": "git",
         "url": "https://github.com/rectorphp/rector.git",
-        "reference": "7923bd5e48f8c26a922df91f7174f5bca2b3671d"
+        "reference": "3f27091368bd935dbbaa8387099792fb20f65f68"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/rectorphp/rector/zipball/7923bd5e48f8c26a922df91f7174f5bca2b3671d",
-        "reference": "7923bd5e48f8c26a922df91f7174f5bca2b3671d",
+        "url": "https://api.github.com/repos/rectorphp/rector/zipball/3f27091368bd935dbbaa8387099792fb20f65f68",
+        "reference": "3f27091368bd935dbbaa8387099792fb20f65f68",
         "shasum": ""
       },
       "require": {
-        "php": "^7.2|^8.0",
-        "phpstan/phpstan": "^1.12.5"
+        "php": "^7.4|^8.0",
+        "phpstan/phpstan": "^2.0.1"
       },
       "conflict": {
         "rector/rector-doctrine": "*",
@@ -5318,7 +5318,7 @@
       "keywords": ["automation", "dev", "migration", "refactoring"],
       "support": {
         "issues": "https://github.com/rectorphp/rector/issues",
-        "source": "https://github.com/rectorphp/rector/tree/1.2.9"
+        "source": "https://github.com/rectorphp/rector/tree/2.0.3"
       },
       "funding": [
         {
@@ -5326,32 +5326,32 @@
           "type": "github"
         }
       ],
-      "time": "2024-11-04T18:26:57+00:00"
+      "time": "2024-12-12T15:22:19+00:00"
     },
     {
       "name": "sebastian/cli-parser",
-      "version": "2.0.1",
+      "version": "3.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/cli-parser.git",
-        "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
+        "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
-        "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+        "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+        "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "2.0-dev"
+          "dev-main": "3.0-dev"
         }
       },
       "autoload": {
@@ -5371,7 +5371,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
         "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
-        "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
+        "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
       },
       "funding": [
         {
@@ -5379,32 +5379,32 @@
           "type": "github"
         }
       ],
-      "time": "2024-03-02T07:12:49+00:00"
+      "time": "2024-07-03T04:41:36+00:00"
     },
     {
       "name": "sebastian/code-unit",
-      "version": "2.0.0",
+      "version": "3.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/code-unit.git",
-        "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
+        "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
-        "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
+        "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
+        "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.5"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "2.0-dev"
+          "dev-main": "3.0-dev"
         }
       },
       "autoload": {
@@ -5423,7 +5423,8 @@
       "homepage": "https://github.com/sebastianbergmann/code-unit",
       "support": {
         "issues": "https://github.com/sebastianbergmann/code-unit/issues",
-        "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
+        "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+        "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2"
       },
       "funding": [
         {
@@ -5431,32 +5432,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T06:58:43+00:00"
+      "time": "2024-12-12T09:59:06+00:00"
     },
     {
       "name": "sebastian/code-unit-reverse-lookup",
-      "version": "3.0.0",
+      "version": "4.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
-        "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
+        "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
-        "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+        "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+        "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "3.0-dev"
+          "dev-main": "4.0-dev"
         }
       },
       "autoload": {
@@ -5474,7 +5475,8 @@
       "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
       "support": {
         "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
-        "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
+        "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+        "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
       },
       "funding": [
         {
@@ -5482,36 +5484,36 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T06:59:15+00:00"
+      "time": "2024-07-03T04:45:54+00:00"
     },
     {
       "name": "sebastian/comparator",
-      "version": "5.0.3",
+      "version": "6.2.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/comparator.git",
-        "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
+        "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
-        "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+        "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739",
+        "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739",
         "shasum": ""
       },
       "require": {
         "ext-dom": "*",
         "ext-mbstring": "*",
-        "php": ">=8.1",
-        "sebastian/diff": "^5.0",
-        "sebastian/exporter": "^5.0"
+        "php": ">=8.2",
+        "sebastian/diff": "^6.0",
+        "sebastian/exporter": "^6.0"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.5"
+        "phpunit/phpunit": "^11.4"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "5.0-dev"
+          "dev-main": "6.2-dev"
         }
       },
       "autoload": {
@@ -5543,7 +5545,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/comparator/issues",
         "security": "https://github.com/sebastianbergmann/comparator/security/policy",
-        "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
+        "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1"
       },
       "funding": [
         {
@@ -5551,33 +5553,33 @@
           "type": "github"
         }
       ],
-      "time": "2024-10-18T14:56:07+00:00"
+      "time": "2024-10-31T05:30:08+00:00"
     },
     {
       "name": "sebastian/complexity",
-      "version": "3.2.0",
+      "version": "4.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/complexity.git",
-        "reference": "68ff824baeae169ec9f2137158ee529584553799"
+        "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
-        "reference": "68ff824baeae169ec9f2137158ee529584553799",
+        "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+        "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
         "shasum": ""
       },
       "require": {
-        "nikic/php-parser": "^4.18 || ^5.0",
-        "php": ">=8.1"
+        "nikic/php-parser": "^5.0",
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "3.2-dev"
+          "dev-main": "4.0-dev"
         }
       },
       "autoload": {
@@ -5597,7 +5599,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/complexity/issues",
         "security": "https://github.com/sebastianbergmann/complexity/security/policy",
-        "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
+        "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
       },
       "funding": [
         {
@@ -5605,33 +5607,33 @@
           "type": "github"
         }
       ],
-      "time": "2023-12-21T08:37:17+00:00"
+      "time": "2024-07-03T04:49:50+00:00"
     },
     {
       "name": "sebastian/diff",
-      "version": "5.1.1",
+      "version": "6.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/diff.git",
-        "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
+        "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
-        "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
+        "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+        "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0",
-        "symfony/process": "^6.4"
+        "phpunit/phpunit": "^11.0",
+        "symfony/process": "^4.2 || ^5"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "5.1-dev"
+          "dev-main": "6.0-dev"
         }
       },
       "autoload": {
@@ -5655,7 +5657,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/diff/issues",
         "security": "https://github.com/sebastianbergmann/diff/security/policy",
-        "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
+        "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
       },
       "funding": [
         {
@@ -5663,27 +5665,27 @@
           "type": "github"
         }
       ],
-      "time": "2024-03-02T07:15:17+00:00"
+      "time": "2024-07-03T04:53:05+00:00"
     },
     {
       "name": "sebastian/environment",
-      "version": "6.1.0",
+      "version": "7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/environment.git",
-        "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
+        "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
-        "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
+        "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+        "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "suggest": {
         "ext-posix": "*"
@@ -5691,7 +5693,7 @@
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "6.1-dev"
+          "dev-main": "7.2-dev"
         }
       },
       "autoload": {
@@ -5711,7 +5713,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/environment/issues",
         "security": "https://github.com/sebastianbergmann/environment/security/policy",
-        "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
+        "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0"
       },
       "funding": [
         {
@@ -5719,34 +5721,34 @@
           "type": "github"
         }
       ],
-      "time": "2024-03-23T08:47:14+00:00"
+      "time": "2024-07-03T04:54:44+00:00"
     },
     {
       "name": "sebastian/exporter",
-      "version": "5.1.2",
+      "version": "6.3.0",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/exporter.git",
-        "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
+        "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
-        "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
+        "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3",
+        "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3",
         "shasum": ""
       },
       "require": {
         "ext-mbstring": "*",
-        "php": ">=8.1",
-        "sebastian/recursion-context": "^5.0"
+        "php": ">=8.2",
+        "sebastian/recursion-context": "^6.0"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.3"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "5.1-dev"
+          "dev-main": "6.1-dev"
         }
       },
       "autoload": {
@@ -5782,7 +5784,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/exporter/issues",
         "security": "https://github.com/sebastianbergmann/exporter/security/policy",
-        "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
+        "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0"
       },
       "funding": [
         {
@@ -5790,35 +5792,35 @@
           "type": "github"
         }
       ],
-      "time": "2024-03-02T07:17:12+00:00"
+      "time": "2024-12-05T09:17:50+00:00"
     },
     {
       "name": "sebastian/global-state",
-      "version": "6.0.2",
+      "version": "7.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/global-state.git",
-        "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
+        "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
-        "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+        "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+        "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1",
-        "sebastian/object-reflector": "^3.0",
-        "sebastian/recursion-context": "^5.0"
+        "php": ">=8.2",
+        "sebastian/object-reflector": "^4.0",
+        "sebastian/recursion-context": "^6.0"
       },
       "require-dev": {
         "ext-dom": "*",
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "6.0-dev"
+          "dev-main": "7.0-dev"
         }
       },
       "autoload": {
@@ -5838,7 +5840,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/global-state/issues",
         "security": "https://github.com/sebastianbergmann/global-state/security/policy",
-        "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
+        "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
       },
       "funding": [
         {
@@ -5846,33 +5848,33 @@
           "type": "github"
         }
       ],
-      "time": "2024-03-02T07:19:19+00:00"
+      "time": "2024-07-03T04:57:36+00:00"
     },
     {
       "name": "sebastian/lines-of-code",
-      "version": "2.0.2",
+      "version": "3.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/lines-of-code.git",
-        "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
+        "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
-        "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
+        "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+        "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
         "shasum": ""
       },
       "require": {
-        "nikic/php-parser": "^4.18 || ^5.0",
-        "php": ">=8.1"
+        "nikic/php-parser": "^5.0",
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "2.0-dev"
+          "dev-main": "3.0-dev"
         }
       },
       "autoload": {
@@ -5892,7 +5894,7 @@
       "support": {
         "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
         "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
-        "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
+        "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
       },
       "funding": [
         {
@@ -5900,34 +5902,34 @@
           "type": "github"
         }
       ],
-      "time": "2023-12-21T08:38:20+00:00"
+      "time": "2024-07-03T04:58:38+00:00"
     },
     {
       "name": "sebastian/object-enumerator",
-      "version": "5.0.0",
+      "version": "6.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/object-enumerator.git",
-        "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
+        "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
-        "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
+        "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+        "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1",
-        "sebastian/object-reflector": "^3.0",
-        "sebastian/recursion-context": "^5.0"
+        "php": ">=8.2",
+        "sebastian/object-reflector": "^4.0",
+        "sebastian/recursion-context": "^6.0"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "5.0-dev"
+          "dev-main": "6.0-dev"
         }
       },
       "autoload": {
@@ -5945,7 +5947,8 @@
       "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
       "support": {
         "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
-        "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
+        "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+        "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
       },
       "funding": [
         {
@@ -5953,32 +5956,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T07:08:32+00:00"
+      "time": "2024-07-03T05:00:13+00:00"
     },
     {
       "name": "sebastian/object-reflector",
-      "version": "3.0.0",
+      "version": "4.0.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/object-reflector.git",
-        "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
+        "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
-        "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
+        "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+        "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "3.0-dev"
+          "dev-main": "4.0-dev"
         }
       },
       "autoload": {
@@ -5996,7 +5999,8 @@
       "homepage": "https://github.com/sebastianbergmann/object-reflector/",
       "support": {
         "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
-        "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
+        "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+        "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
       },
       "funding": [
         {
@@ -6004,32 +6008,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T07:06:18+00:00"
+      "time": "2024-07-03T05:01:32+00:00"
     },
     {
       "name": "sebastian/recursion-context",
-      "version": "5.0.0",
+      "version": "6.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/recursion-context.git",
-        "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
+        "reference": "694d156164372abbd149a4b85ccda2e4670c0e16"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
-        "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
+        "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16",
+        "reference": "694d156164372abbd149a4b85ccda2e4670c0e16",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.0"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "5.0-dev"
+          "dev-main": "6.0-dev"
         }
       },
       "autoload": {
@@ -6055,7 +6059,8 @@
       "homepage": "https://github.com/sebastianbergmann/recursion-context",
       "support": {
         "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-        "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
+        "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+        "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2"
       },
       "funding": [
         {
@@ -6063,32 +6068,32 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T07:05:40+00:00"
+      "time": "2024-07-03T05:10:34+00:00"
     },
     {
       "name": "sebastian/type",
-      "version": "4.0.0",
+      "version": "5.1.0",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/type.git",
-        "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
+        "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
-        "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
+        "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+        "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "require-dev": {
-        "phpunit/phpunit": "^10.0"
+        "phpunit/phpunit": "^11.3"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "4.0-dev"
+          "dev-main": "5.1-dev"
         }
       },
       "autoload": {
@@ -6107,7 +6112,8 @@
       "homepage": "https://github.com/sebastianbergmann/type",
       "support": {
         "issues": "https://github.com/sebastianbergmann/type/issues",
-        "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
+        "security": "https://github.com/sebastianbergmann/type/security/policy",
+        "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
       },
       "funding": [
         {
@@ -6115,29 +6121,29 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-03T07:10:45+00:00"
+      "time": "2024-09-17T13:12:04+00:00"
     },
     {
       "name": "sebastian/version",
-      "version": "4.0.1",
+      "version": "5.0.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianbergmann/version.git",
-        "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
+        "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
-        "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+        "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
+        "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
         "shasum": ""
       },
       "require": {
-        "php": ">=8.1"
+        "php": ">=8.2"
       },
       "type": "library",
       "extra": {
         "branch-alias": {
-          "dev-main": "4.0-dev"
+          "dev-main": "5.0-dev"
         }
       },
       "autoload": {
@@ -6156,7 +6162,8 @@
       "homepage": "https://github.com/sebastianbergmann/version",
       "support": {
         "issues": "https://github.com/sebastianbergmann/version/issues",
-        "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
+        "security": "https://github.com/sebastianbergmann/version/security/policy",
+        "source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
       },
       "funding": [
         {
@@ -6164,7 +6171,7 @@
           "type": "github"
         }
       ],
-      "time": "2023-02-07T11:34:05+00:00"
+      "time": "2024-10-09T05:16:32+00:00"
     },
     {
       "name": "sebastianfeldmann/camino",
@@ -6219,16 +6226,16 @@
     },
     {
       "name": "sebastianfeldmann/cli",
-      "version": "3.4.1",
+      "version": "3.4.2",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianfeldmann/cli.git",
-        "reference": "8a932e99e9455981fb32fa6c085492462fe8f8cf"
+        "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianfeldmann/cli/zipball/8a932e99e9455981fb32fa6c085492462fe8f8cf",
-        "reference": "8a932e99e9455981fb32fa6c085492462fe8f8cf",
+        "url": "https://api.github.com/repos/sebastianfeldmann/cli/zipball/6fa122afd528dae7d7ec988a604aa6c600f5d9b5",
+        "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5",
         "shasum": ""
       },
       "require": {
@@ -6261,7 +6268,7 @@
       "keywords": ["cli"],
       "support": {
         "issues": "https://github.com/sebastianfeldmann/cli/issues",
-        "source": "https://github.com/sebastianfeldmann/cli/tree/3.4.1"
+        "source": "https://github.com/sebastianfeldmann/cli/tree/3.4.2"
       },
       "funding": [
         {
@@ -6269,20 +6276,20 @@
           "type": "github"
         }
       ],
-      "time": "2021-12-20T14:59:49+00:00"
+      "time": "2024-11-26T10:19:01+00:00"
     },
     {
       "name": "sebastianfeldmann/git",
-      "version": "3.11.0",
+      "version": "3.11.1",
       "source": {
         "type": "git",
         "url": "https://github.com/sebastianfeldmann/git.git",
-        "reference": "5cb1ea94f65c7420419abe8f12c45cc7eb094790"
+        "reference": "96b9f384d45106f757df98a74c11b42b393ff18f"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/5cb1ea94f65c7420419abe8f12c45cc7eb094790",
-        "reference": "5cb1ea94f65c7420419abe8f12c45cc7eb094790",
+        "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/96b9f384d45106f757df98a74c11b42b393ff18f",
+        "reference": "96b9f384d45106f757df98a74c11b42b393ff18f",
         "shasum": ""
       },
       "require": {
@@ -6319,7 +6326,7 @@
       "keywords": ["git"],
       "support": {
         "issues": "https://github.com/sebastianfeldmann/git/issues",
-        "source": "https://github.com/sebastianfeldmann/git/tree/3.11.0"
+        "source": "https://github.com/sebastianfeldmann/git/tree/3.11.1"
       },
       "funding": [
         {
@@ -6327,20 +6334,66 @@
           "type": "github"
         }
       ],
-      "time": "2024-01-23T09:11:14+00:00"
+      "time": "2024-11-26T18:37:20+00:00"
+    },
+    {
+      "name": "staabm/side-effects-detector",
+      "version": "1.0.5",
+      "source": {
+        "type": "git",
+        "url": "https://github.com/staabm/side-effects-detector.git",
+        "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+      },
+      "dist": {
+        "type": "zip",
+        "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+        "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+        "shasum": ""
+      },
+      "require": {
+        "ext-tokenizer": "*",
+        "php": "^7.4 || ^8.0"
+      },
+      "require-dev": {
+        "phpstan/extension-installer": "^1.4.3",
+        "phpstan/phpstan": "^1.12.6",
+        "phpunit/phpunit": "^9.6.21",
+        "symfony/var-dumper": "^5.4.43",
+        "tomasvotruba/type-coverage": "1.0.0",
+        "tomasvotruba/unused-public": "1.0.0"
+      },
+      "type": "library",
+      "autoload": {
+        "classmap": ["lib/"]
+      },
+      "notification-url": "https://packagist.org/downloads/",
+      "license": ["MIT"],
+      "description": "A static analysis tool to detect side effects in PHP code",
+      "keywords": ["static analysis"],
+      "support": {
+        "issues": "https://github.com/staabm/side-effects-detector/issues",
+        "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+      },
+      "funding": [
+        {
+          "url": "https://github.com/staabm",
+          "type": "github"
+        }
+      ],
+      "time": "2024-10-20T05:08:20+00:00"
     },
     {
       "name": "symfony/console",
-      "version": "v7.1.7",
+      "version": "v7.2.1",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/console.git",
-        "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a"
+        "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a",
-        "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a",
+        "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+        "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
         "shasum": ""
       },
       "require": {
@@ -6395,7 +6448,7 @@
       "homepage": "https://symfony.com",
       "keywords": ["cli", "command-line", "console", "terminal"],
       "support": {
-        "source": "https://github.com/symfony/console/tree/v7.1.7"
+        "source": "https://github.com/symfony/console/tree/v7.2.1"
       },
       "funding": [
         {
@@ -6411,20 +6464,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-11-05T15:34:55+00:00"
+      "time": "2024-12-11T03:49:26+00:00"
     },
     {
       "name": "symfony/event-dispatcher",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/event-dispatcher.git",
-        "reference": "87254c78dd50721cfd015b62277a8281c5589702"
+        "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702",
-        "reference": "87254c78dd50721cfd015b62277a8281c5589702",
+        "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1",
+        "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1",
         "shasum": ""
       },
       "require": {
@@ -6471,7 +6524,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/v7.1.6"
+        "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0"
       },
       "funding": [
         {
@@ -6487,20 +6540,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-09-25T14:20:29+00:00"
+      "time": "2024-09-25T14:21:43+00:00"
     },
     {
       "name": "symfony/event-dispatcher-contracts",
-      "version": "v3.5.0",
+      "version": "v3.5.1",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/event-dispatcher-contracts.git",
-        "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50"
+        "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50",
-        "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50",
+        "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f",
+        "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f",
         "shasum": ""
       },
       "require": {
@@ -6545,7 +6598,7 @@
         "standards"
       ],
       "support": {
-        "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0"
+        "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1"
       },
       "funding": [
         {
@@ -6561,20 +6614,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-04-18T09:32:20+00:00"
+      "time": "2024-09-25T14:20:29+00:00"
     },
     {
       "name": "symfony/filesystem",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/filesystem.git",
-        "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4"
+        "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4",
-        "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4",
+        "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+        "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
         "shasum": ""
       },
       "require": {
@@ -6607,7 +6660,7 @@
       "description": "Provides basic utilities for the filesystem",
       "homepage": "https://symfony.com",
       "support": {
-        "source": "https://github.com/symfony/filesystem/tree/v7.1.6"
+        "source": "https://github.com/symfony/filesystem/tree/v7.2.0"
       },
       "funding": [
         {
@@ -6623,20 +6676,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-10-25T15:11:02+00:00"
+      "time": "2024-10-25T15:15:23+00:00"
     },
     {
       "name": "symfony/finder",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/finder.git",
-        "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
+        "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
-        "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
+        "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49",
+        "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49",
         "shasum": ""
       },
       "require": {
@@ -6667,7 +6720,7 @@
       "description": "Finds files and directories via an intuitive fluent interface",
       "homepage": "https://symfony.com",
       "support": {
-        "source": "https://github.com/symfony/finder/tree/v7.1.6"
+        "source": "https://github.com/symfony/finder/tree/v7.2.0"
       },
       "funding": [
         {
@@ -6683,20 +6736,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-10-01T08:31:23+00:00"
+      "time": "2024-10-23T06:56:12+00:00"
     },
     {
       "name": "symfony/options-resolver",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/options-resolver.git",
-        "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
+        "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
-        "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
+        "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
+        "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
         "shasum": ""
       },
       "require": {
@@ -6726,7 +6779,7 @@
       "homepage": "https://symfony.com",
       "keywords": ["config", "configuration", "options"],
       "support": {
-        "source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
+        "source": "https://github.com/symfony/options-resolver/tree/v7.2.0"
       },
       "funding": [
         {
@@ -6742,7 +6795,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-09-25T14:20:29+00:00"
+      "time": "2024-11-20T11:17:29+00:00"
     },
     {
       "name": "symfony/polyfill-intl-grapheme",
@@ -6767,8 +6820,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -6841,8 +6894,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -6913,8 +6966,8 @@
       "type": "library",
       "extra": {
         "thanks": {
-          "name": "symfony/polyfill",
-          "url": "https://github.com/symfony/polyfill"
+          "url": "https://github.com/symfony/polyfill",
+          "name": "symfony/polyfill"
         }
       },
       "autoload": {
@@ -6960,16 +7013,16 @@
     },
     {
       "name": "symfony/process",
-      "version": "v7.1.7",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/process.git",
-        "reference": "9b8a40b7289767aa7117e957573c2a535efe6585"
+        "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585",
-        "reference": "9b8a40b7289767aa7117e957573c2a535efe6585",
+        "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+        "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
         "shasum": ""
       },
       "require": {
@@ -6997,7 +7050,7 @@
       "description": "Executes commands in sub-processes",
       "homepage": "https://symfony.com",
       "support": {
-        "source": "https://github.com/symfony/process/tree/v7.1.7"
+        "source": "https://github.com/symfony/process/tree/v7.2.0"
       },
       "funding": [
         {
@@ -7013,20 +7066,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-11-06T09:25:12+00:00"
+      "time": "2024-11-06T14:24:19+00:00"
     },
     {
       "name": "symfony/service-contracts",
-      "version": "v3.5.0",
+      "version": "v3.5.1",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/service-contracts.git",
-        "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
+        "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
-        "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
+        "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+        "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
         "shasum": ""
       },
       "require": {
@@ -7076,7 +7129,7 @@
         "standards"
       ],
       "support": {
-        "source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
+        "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
       },
       "funding": [
         {
@@ -7092,20 +7145,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-04-18T09:32:20+00:00"
+      "time": "2024-09-25T14:20:29+00:00"
     },
     {
       "name": "symfony/stopwatch",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/stopwatch.git",
-        "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05"
+        "reference": "696f418b0d722a4225e1c3d95489d262971ca924"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05",
-        "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05",
+        "url": "https://api.github.com/repos/symfony/stopwatch/zipball/696f418b0d722a4225e1c3d95489d262971ca924",
+        "reference": "696f418b0d722a4225e1c3d95489d262971ca924",
         "shasum": ""
       },
       "require": {
@@ -7134,7 +7187,7 @@
       "description": "Provides a way to profile code",
       "homepage": "https://symfony.com",
       "support": {
-        "source": "https://github.com/symfony/stopwatch/tree/v7.1.6"
+        "source": "https://github.com/symfony/stopwatch/tree/v7.2.0"
       },
       "funding": [
         {
@@ -7150,20 +7203,20 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-09-25T14:20:29+00:00"
+      "time": "2024-09-25T14:21:43+00:00"
     },
     {
       "name": "symfony/string",
-      "version": "v7.1.6",
+      "version": "v7.2.0",
       "source": {
         "type": "git",
         "url": "https://github.com/symfony/string.git",
-        "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626"
+        "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626",
-        "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626",
+        "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+        "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
         "shasum": ""
       },
       "require": {
@@ -7208,7 +7261,7 @@
       "homepage": "https://symfony.com",
       "keywords": ["grapheme", "i18n", "string", "unicode", "utf-8", "utf8"],
       "support": {
-        "source": "https://github.com/symfony/string/tree/v7.1.6"
+        "source": "https://github.com/symfony/string/tree/v7.2.0"
       },
       "funding": [
         {
@@ -7224,7 +7277,7 @@
           "type": "tidelift"
         }
       ],
-      "time": "2024-09-25T14:20:29+00:00"
+      "time": "2024-11-13T13:31:26+00:00"
     },
     {
       "name": "symplify/coding-standard",
@@ -7285,16 +7338,16 @@
     },
     {
       "name": "symplify/easy-coding-standard",
-      "version": "12.3.6",
+      "version": "12.5.4",
       "source": {
         "type": "git",
         "url": "https://github.com/easy-coding-standard/easy-coding-standard.git",
-        "reference": "c0f378782d06dfd21c66c3024e9d28f4e737645e"
+        "reference": "5673ecbc03eef9d7b2f563819c80e8e1ce0161be"
       },
       "dist": {
         "type": "zip",
-        "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/c0f378782d06dfd21c66c3024e9d28f4e737645e",
-        "reference": "c0f378782d06dfd21c66c3024e9d28f4e737645e",
+        "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/5673ecbc03eef9d7b2f563819c80e8e1ce0161be",
+        "reference": "5673ecbc03eef9d7b2f563819c80e8e1ce0161be",
         "shasum": ""
       },
       "require": {
@@ -7319,7 +7372,7 @@
       "keywords": ["Code style", "automation", "fixer", "static analysis"],
       "support": {
         "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues",
-        "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.6"
+        "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.4"
       },
       "funding": [
         {
@@ -7331,7 +7384,7 @@
           "type": "github"
         }
       ],
-      "time": "2024-10-06T08:27:28+00:00"
+      "time": "2024-12-12T15:36:04+00:00"
     },
     {
       "name": "symplify/rule-doc-generator-contracts",
diff --git a/docs/src/content/docs/en/plugins/hooks.mdx b/docs/src/content/docs/en/plugins/hooks.mdx
index cfa1e22505b4562e6eb06b0ebca77ab1e2225347..db7bbd0ca991526db7f1d292fccb0d74c5edf5df 100644
--- a/docs/src/content/docs/en/plugins/hooks.mdx
+++ b/docs/src/content/docs/en/plugins/hooks.mdx
@@ -37,7 +37,7 @@ RSS feed.
 Here is a good place to add new tags to the generated channel.
 
 ```php
-public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
+public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
 {
     // …
 }
@@ -65,7 +65,7 @@ feed.
 Here is a good place to add new tags to the generated item.
 
 ```php
-public function rssAfterItem(Epsiode $episode, SimpleRSSElement $item): void
+public function rssAfterItem(Epsiode $episode, RssFeed $item): void
 {
     // …
 }
@@ -75,11 +75,11 @@ public function rssAfterItem(Epsiode $episode, SimpleRSSElement $item): void
 
 This hook is executed in the public pages' `<head>` tag.
 
-This is a good place to add meta tags and third-party scripts to Castopod's
-public pages.
+This is a good place to add meta tags, custom styles, and third-party scripts to
+Castopod's public pages.
 
 ```php
-public function siteHead(): void
+public function siteHead(HtmlHead $head): void
 {
     // …
 }
diff --git a/docs/src/content/docs/en/plugins/manifest.mdx b/docs/src/content/docs/en/plugins/manifest.mdx
index d8074cffba82e283c96223731c03a298347b43ea..7ff205827b761dbb6b1980a8a4bc81685cac012e 100644
--- a/docs/src/content/docs/en/plugins/manifest.mdx
+++ b/docs/src/content/docs/en/plugins/manifest.mdx
@@ -101,16 +101,16 @@ each property being a field key and the value being a `Field` object.
 
 A field is a form element:
 
-| Property           | Type                                                                                                                                                                     | Note                                                               |
-| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
-| `type`             | `checkbox` \| `datetime` \| `email` \| `group` \| `markdown` \| `number` \| `radio-group` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text`                                                  |
-| `label` (required) | `string`                                                                                                                                                                 | Can be translated (see i18n)                                       |
-| `hint`             | `string`                                                                                                                                                                 | Can be translated (see i18n)                                       |
-| `helper`           | `string`                                                                                                                                                                 | Can be translated (see i18n)                                       |
-| `optional`         | `boolean`                                                                                                                                                                | Default is `false`                                                 |
-| `options`          | `Options`                                                                                                                                                                | Required for `radio-group`, `select-multiple`, and `select` types. |
-| `multiple`         | `boolean`                                                                                                                                                                | Default is `false`                                                 |
-| `fields`           | `Array<string, Field>`                                                                                                                                                   | Required for `group` type                                          |
+| Property           | Type                                                                                                                                                                                        | Note                                                               |
+| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
+| `type`             | `checkbox` \| `datetime` \| `email` \| `group` \| `html` \| `markdown` \| `number` \| `radio-group` \| `rss` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text`                                                  |
+| `label` (required) | `string`                                                                                                                                                                                    | Can be translated (see i18n)                                       |
+| `hint`             | `string`                                                                                                                                                                                    | Can be translated (see i18n)                                       |
+| `helper`           | `string`                                                                                                                                                                                    | Can be translated (see i18n)                                       |
+| `optional`         | `boolean`                                                                                                                                                                                   | Default is `false`                                                 |
+| `options`          | `Options`                                                                                                                                                                                   | Required for `radio-group`, `select-multiple`, and `select` types. |
+| `multiple`         | `boolean`                                                                                                                                                                                   | Default is `false`                                                 |
+| `fields`           | `Array<string, Field>`                                                                                                                                                                      | Required for `group` type                                          |
 
 #### Options object
 
diff --git a/docs/src/content/docs/zh-hans/getting-started/auth.mdx b/docs/src/content/docs/zh-hans/getting-started/auth.mdx
index 6685fe62796db59c59799b1189fe34f7c6c1fcf2..ced7ab7e537f34554579ec48e0bad83ad462a623 100644
--- a/docs/src/content/docs/zh-hans/getting-started/auth.mdx
+++ b/docs/src/content/docs/zh-hans/getting-started/auth.mdx
@@ -2,8 +2,9 @@
 title: 验证 & 授权
 ---
 
-Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规则。 角色和权限
-在两个级别上定义: Roles and permissions are defined at two levels:
+Castopod 使用 `codeigniter/shield`
+处理身份验证和授权 与自定义规则。 角色和权限在两个级别上定义: Roles and
+permissions are defined at two levels:
 
 1. [实例范围](#1-instance-wide-roles-and-permissions)
 2. [每个播客](#2-per-podcast-roles-and-permissions)
diff --git a/docs/src/content/docs/zh-hans/getting-started/docker.mdx b/docs/src/content/docs/zh-hans/getting-started/docker.mdx
index 88a19839401931fd33531aa22fe19858b2273ded..ecac3eaf334d67a06b677b5b9094e4a559d3da59 100644
--- a/docs/src/content/docs/zh-hans/getting-started/docker.mdx
+++ b/docs/src/content/docs/zh-hans/getting-started/docker.mdx
@@ -4,15 +4,12 @@ title: 官方 Docker 镜像
 
 Castopod 在其自动构建期间会将 3 个 Docker 映像推送到 Docker Hub :
 
-- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使
-  用 nginx 单元的整合 Castopod 镜像
-- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含
-  所有 Castopod 依赖关系
-- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod
-  的 Nginx 配置
-
-此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理
-器。 A Redis database can be added as a cache handler.
+- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使用 nginx 单元的整合 Castopod 镜像
+- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含所有 Castopod 依赖关系
+- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod 的 Nginx 配置
+
+此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理器。 A
+Redis database can be added as a cache handler.
 
 ## 目前支持的标签
 
diff --git a/docs/src/content/docs/zh-hans/getting-started/install.mdx b/docs/src/content/docs/zh-hans/getting-started/install.mdx
index 42e30a5141116f613e8f0afb9edeb545ab0fa215..00727621761d3560b159c6159bfedb50b6895fe4 100644
--- a/docs/src/content/docs/zh-hans/getting-started/install.mdx
+++ b/docs/src/content/docs/zh-hans/getting-started/install.mdx
@@ -35,9 +35,8 @@ PHP version 8.3 or higher is required, with the following extensions installed:
 
 > 我们建议使用 [MariaDB](https://mariadb.org)。
 
-你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这
-些,请与你的服务器管理员联系。 If you do not have these, please contact your
-server administrator.
+你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这些,请与你的服务器管理员联系。 If
+you do not have these, please contact your server administrator.
 
 #### 权限
 
@@ -46,8 +45,8 @@ server administrator.
 
 ### (可选)FFmpeg v4.1.8 或更高版本,用于视频素材
 
-如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/) 4.1.8 或更高版本。
-必须安装以下扩展: The following extensions must be installed:
+如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/)
+4.1.8 或更高版本。必须安装以下扩展: The following extensions must be installed:
 
 - **FreeType 2** 来自库
   [gd](https://www.php.net/manual/en/image.installation.php)
@@ -63,11 +62,11 @@ server administrator.
 ### Pre-requisites
 
 0. 需要一台已经实现 [环境要求](#requirements)的 Web 服务器
-1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息,
-   请参阅 [MySQL 兼容数据库](#mysql-compatible-database))。
+1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息,请参阅
+   [MySQL 兼容数据库](#mysql-compatible-database))。
 2. 使用 _SSL 证书_ 在您的域激活 HTTPS。
-3. 下载最新的 [Castopod](https://castopod.org/) 到 web 服务器并解压(如果尚未下
-   载)。
+3. 下载最新的 [Castopod](https://castopod.org/)
+   到 web 服务器并解压(如果尚未下载)。
    - ⚠️ 将 web 服务器根目录设置为 `castopod` 文件夹中的 `public/` 子文件夹。
 4. 在 Web 服务器上为各种后台进程添加 **cron 任务** (相应地替换路径):
 
@@ -85,16 +84,16 @@ server administrator.
 
 ### (推荐) 安装向导
 
-1. 前往你最喜欢的浏览器并跳转至安装向导页面
-   (`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。
+1. 前往你最喜欢的浏览器并跳转至安装向导页面 (`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。
 2. 请按照屏幕上的说明进行操作。
 3. 开始播客!
 
 <Aside>
 
-The install script writes a `.env` file in the package root. 安装脚本将会在根目
-录中创建一个 `.env` 文件并写入数据。 如果你不能执行安装向导,那么可以基于
-`.env.example` 文件手动创建和编辑 `.env` 文件。
+The install script writes a `.env` file in the package
+root. 安装脚本将会在根目录中创建一个 `.env`
+文件并写入数据。 如果你不能执行安装向导,那么可以基于 `.env.example`
+文件手动创建和编辑 `.env` 文件。
 
 </Aside>
 
@@ -147,9 +146,9 @@ email.SMTPPass="你的邮件密码"
 
 ### 媒体存储
 
-By default, files are saved to the `public/media` folder using the file system.
-默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将 `media`
-文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示:
+By default, files are saved to the `public/media` folder using the file
+system. 默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将
+`media` 文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示:
 
 ```ini
 # […]
@@ -194,9 +193,9 @@ media.s3.region="your_s3_region"
 
 ### 使用 YunoHost 安装
 
-[YunoHost](https://yunohost.org/) 是一个基于 Debian GNU/Linux 的发行版,由免费和
-开源软件包组成。 它可以为你解决自托管的困难。 It manages the hardships of
-self-hosting for you.
+[YunoHost](https://yunohost.org/) 是一个基于 Debian
+GNU/Linux 的发行版,由免费和开源软件包组成。 它可以为你解决自托管的困难。 It
+manages the hardships of self-hosting for you.
 
 <div class="flex flex-wrap items-center gap-4">
 
diff --git a/docs/src/content/docs/zh-hans/getting-started/security.mdx b/docs/src/content/docs/zh-hans/getting-started/security.mdx
index 78c367e6f0acde95c4b4c62843665594e9b76045..653b16552e740600883f1aa72952112dc60ef91e 100644
--- a/docs/src/content/docs/zh-hans/getting-started/security.mdx
+++ b/docs/src/content/docs/zh-hans/getting-started/security.mdx
@@ -5,8 +5,7 @@ title: 安全问题
 Castopod 构建于 [CodeIgniter4](https://codeigniter.com/), PHP 框架上,鼓励
 [更好的安全实践](https://codeigniter.com/user_guide/concepts/security.html)。
 
-为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检
-查所有的 Castopod 文件权限(避免任何之前的权限错误):
+为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检查所有的 Castopod 文件权限(避免任何之前的权限错误):
 
 - `writable/` 文件夹权限为 **可读** 和 **可写**。
 - `public/media/` 文件夹权限为 **可读** 和 **可写**。
diff --git a/docs/src/content/docs/zh-hans/getting-started/update.mdx b/docs/src/content/docs/zh-hans/getting-started/update.mdx
index 6672813f3b1ed56de3631ae1dc294ea244a288b2..693c6bf5d56e89b2f481b99f1f04d5c1baa704cc 100644
--- a/docs/src/content/docs/zh-hans/getting-started/update.mdx
+++ b/docs/src/content/docs/zh-hans/getting-started/update.mdx
@@ -4,8 +4,7 @@ title: 如何更新 Castopod ?
 
 import { Aside } from "@astrojs/starlight/components";
 
-安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误
-🐛 和性能提升 ⚡。
+安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误 🐛 和性能提升 ⚡。
 
 ## 更新说明
 
@@ -13,14 +12,14 @@ import { Aside } from "@astrojs/starlight/components";
 
    - 参看. [我应该在更新前进行备份吗?](#should-i-make-a-backup-before-updating)
 
-1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases) 和 查
-   看您的实例是否是最新的 Castopod 版本
+1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases)
+   和 查看您的实例是否是最新的 Castopod 版本
 
    - 参看
      [我在哪里可以找到我的 Castopod 版本?](#where-can-i-find-my-castopod-version)
 
-2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz` 压缩包之间
-   选择
+2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz`
+   压缩包之间选择
 
    - ⚠️ 请确保你下载的是 Castopod 软件包而 **不是** 源代码
    - 请注意,你还可以从 [castopod.org](https://castopod.org/)
@@ -83,8 +82,9 @@ them sequentially, from the oldest to the newest.
 
 1. 下载最新版本,覆盖您的文件,同时保留 `.env` 文件和 `public/media` 文件夹。
 
-2. 从 `v1.0.0-alpha.43` 开始,按顺序执行每个版本更新指令(从老版本到 最新版本),
-   然后是 `v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。
+2. 从 `v1.0.0-alpha.43`
+   开始,按顺序执行每个版本更新指令(从老版本到 最新版本),然后是
+   `v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。
 
 3. ✨ 享受你的新实例, 你已经更新完毕!
 
diff --git a/docs/src/content/docs/zh-hans/index.mdx b/docs/src/content/docs/zh-hans/index.mdx
index 3dd1b6a5b1e9dfefb5b2e71dd8eb4606330b1a4e..0dc25fb1e6e8bc8f2f3c482e0f68a03f3b0d1a2c 100644
--- a/docs/src/content/docs/zh-hans/index.mdx
+++ b/docs/src/content/docs/zh-hans/index.mdx
@@ -4,11 +4,10 @@ title: 欢迎 👋
 
 import { LinkCard } from "@astrojs/starlight/components";
 
-Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作
-的。
+Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作的。
 
-Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构建, 这是一
-个强大的 PHP 框架,并且占用极小。
+Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/)
+构建, 这是一个强大的 PHP 框架,并且占用极小。
 
 <LinkCard title="安装" href="./getting-started/install" />
 
@@ -44,28 +43,24 @@ Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构
 - 📤  也支持将播客移出 Castopod
 - 🔀  多租户:根据需要托管任意数量的播客
 - 👥  多用户:添加贡献者并设置角色
-- 🌎  i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语
-  ...[还有更多](https://translate.castopod.org)! and
+- 🌎
+   i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语 ...[还有更多](https://translate.castopod.org)! and
   [many more](https://translate.castopod.org)!
 
 ## 创作动机
 
-播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上
-并在线共享。
+播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上并在线共享。
 
 事实上,它是唯一长期保持这种状态的媒体之一。
 
-随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想
-法,或是让听众获得更好的内容。
+随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想法,或是让听众获得更好的内容。
 
 随着播客的使用越来越广泛,一些公司正试图控制播客与集中化。
 
-Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放,
-确保播客可以用创造力表达自己。
+Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放,确保播客可以用创造力表达自己。
 
-此项目由开源社区推动的,特别是
-由[联邦宇宙](https://fediverse.party/en/fediverse/) 和
-[播客 2.0](https://podcastindex.org/) 推动。
+此项目由开源社区推动的,特别是由[联邦宇宙](https://fediverse.party/en/fediverse/)
+和 [播客 2.0](https://podcastindex.org/) 推动。
 
 ## 与其他解决方案的对比
 
@@ -75,51 +70,42 @@ gauge whether Castopod is the right fit for you.
 
 ### Castopod 对比 Wordpress
 
-Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面,
-确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用
-性以及运行它的网站数量。 In some ways this is true. And actually, Castopod was
-greatly inspired by the Wordpress ecosystem, seeing the ease of adoption from
-the community and the number of websites running it.
+Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面,确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用性以及运行它的网站数量。 In
+some ways this is true. And actually, Castopod was greatly inspired by the
+Wordpress ecosystem, seeing the ease of adoption from the community and the
+number of websites running it.
 
-就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以
-在大多数 Web 服务器上轻松安装。
+就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以在大多数 Web 服务器上轻松安装。
 
-Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的
-CMS,可以帮助你在线访问任何类型的网站。 It is a full fledged CMS that helps you
-get any type of website online.
+Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的 CMS,可以帮助你在线访问任何类型的网站。 It
+is a full fledged CMS that helps you get any type of website online.
 
-另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何
-插件即可轻松开始播客之旅。 You don't need any plugin to get you started on your
-podcasting journey.
+另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何插件即可轻松开始播客之旅。 You
+don't need any plugin to get you started on your podcasting journey.
 
 还拥有对播客的独特优化:从播客的创建和新剧集的发布一直到广播,营销和分析。
 
-最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环
-境!
+最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环境!
 
 ### Castopod 对比 Funkwhale
 
-Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一
-样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。Just
+Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。Just
 as Castopod, Funkwhale is on the fediverse, a decentralized social network
 allowing interoperability between the two.
 
 Funkwhale 最初是围绕音乐制作的。 后来,随着项目的发展,引入了托管播客的能力。And
 later on, as the project evolved, the ability to host podcasts was introduced.
 
-与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播
-客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This allows
-easier implementation for features related to the podcasting ecosystem, such as
-the podcasting 2.0 features (transcripts, chapters, locations, persons, …).
+与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This
+allows easier implementation for features related to the podcasting ecosystem,
+such as the podcasting 2.0 features (transcripts, chapters, locations, persons,
+…).
 
-因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请
-使用 Castopod。
+因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请使用 Castopod。
 
 ### Castopod 与其他播客
 
-有许多非常棒地解决方案可供你托管播客,并
-且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一
-样!
+有许多非常棒地解决方案可供你托管播客,并且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一样!
 
 这些解决方案各不相同,你可以对比 [功能列表](#features)。
 
@@ -129,34 +115,30 @@ the podcasting 2.0 features (transcripts, chapters, locations, persons, …).
   full control over what you produce. Also, as it is open-source, you can even
   customize it as you wish.
 
-- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播
-  客 2.0 功能集成的解决方案,希望弥合两者之间的差距。
+- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播客 2.0 功能集成的解决方案,希望弥合两者之间的差距。
 
 ## 贡献
 
-喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你
-入门。
+喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你入门。
 
 ### 行为准则
 
 Castopod has adopted a Code of Conduct that we expect project participants to
-adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准
-则。 请阅
-读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md)
+adhere to.
+Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准则。 请阅读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md)
 以便了解哪些行为被允许,哪些行为不会被容忍。
 
 ### 贡献指南
 
-阅读我们的 [贡献指南](../contributing/guidelines.md) ,了解我们的开发过程。 提出
-错 误修正和改进想法,以及如何构建和测试 Castopod 。
+阅读我们的 [贡献指南](../contributing/guidelines.md)
+,了解我们的开发过程。 提出错 误修正和改进想法,以及如何构建和测试 Castopod 。
 
 ## 联系
 
 你可以联系我们寻求帮助或提出任何问题:
 
 - [Discord](https://castopod.org/discord) (用于与开发人员和社区直接互动)
-- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请
-  求和错误报告)
+- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请求和错误报告)
 
 或者,你可以在社交媒体上关注我们,以获取有关 Castopod 的新闻:
 
@@ -168,8 +150,8 @@ adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与
 ## 赞助商
 
 The ongoing development of Castopod is made possible with the support of its
-backers. Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考
-虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute).
+backers.
+Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute).
 
 [![Ad Aures Logo](../../../assets/images/sponsors/adaures.svg)](https://adaures.com/)
 
diff --git a/docs/src/content/docs/zh-hant/getting-started/auth.mdx b/docs/src/content/docs/zh-hant/getting-started/auth.mdx
index b750816c24ac07da297830a2a67f260e2e7f3664..2b9f3f0a3592b58141aaaa084b9e86d93fcf534c 100644
--- a/docs/src/content/docs/zh-hant/getting-started/auth.mdx
+++ b/docs/src/content/docs/zh-hant/getting-started/auth.mdx
@@ -2,8 +2,9 @@
 title: 認證 & 授權
 ---
 
-Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規則。 腳色和權限
-在定義為兩個層級: Roles and permissions are defined at two levels:
+Castopod 使用 `codeigniter/shield`
+處理身分認證和授權 與自定義規則。 腳色和權限在定義為兩個層級: Roles and
+permissions are defined at two levels:
 
 1. [實例範圍](#1-instance-wide-roles-and-permissions)
 2. [每個播客](#2-per-podcast-roles-and-permissions)
diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php
index 5407eb7483dda164826cdb992ea94cde148b6c93..e11a8eadf87955c9d06c18bd2a3ff489fb27a453 100644
--- a/modules/Admin/Config/Routes.php
+++ b/modules/Admin/Config/Routes.php
@@ -16,7 +16,7 @@ $routes->add('scheduled-video-clips', 'SchedulerController::generateVideoClips',
 // Admin area routes
 $routes->group(
     config('Admin')
-->gateway,
+        ->gateway,
     [
         'namespace' => 'Modules\Admin\Controllers',
     ],
diff --git a/modules/Admin/Controllers/AboutController.php b/modules/Admin/Controllers/AboutController.php
index 4b69352c6f8b77aa51aa8d28b378ba13fe392217..f355c7853061ade023c4ac102baec5bbfe41991a 100644
--- a/modules/Admin/Controllers/AboutController.php
+++ b/modules/Admin/Controllers/AboutController.php
@@ -25,6 +25,7 @@ class AboutController extends BaseController
             'languages'   => implode(', ', config('App')->supportedLocales),
         ];
 
+        $this->setHtmlHead(lang('AboutCastopod.title'));
         return view('settings/about', [
             'info' => $instanceInfo,
         ]);
diff --git a/modules/Admin/Controllers/BaseController.php b/modules/Admin/Controllers/BaseController.php
index 4d2fa068f06c9f5aa86103a3535c0a6d3d129d26..28c3525f80801e2421d0b671e908c6da432dd279 100644
--- a/modules/Admin/Controllers/BaseController.php
+++ b/modules/Admin/Controllers/BaseController.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Modules\Admin\Controllers;
 
+use App\Libraries\HtmlHead;
 use CodeIgniter\Controller;
 use CodeIgniter\HTTP\IncomingRequest;
 use CodeIgniter\HTTP\RequestInterface;
@@ -41,4 +42,16 @@ abstract class BaseController extends Controller
 
         Theme::setTheme('admin');
     }
+
+    protected function setHtmlHead(string $title): void
+    {
+        /** @var HtmlHead $head */
+        $head = service('html_head');
+
+        $head
+            ->title($title . ' | Castopod Admin')
+            ->description(
+                'Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.'
+            );
+    }
 }
diff --git a/modules/Admin/Controllers/DashboardController.php b/modules/Admin/Controllers/DashboardController.php
index 7d6f48212c8d65dc25a94061289ea496abe3b55e..2197777593c3a0a4d4169f747a63836bbabcf4bb 100644
--- a/modules/Admin/Controllers/DashboardController.php
+++ b/modules/Admin/Controllers/DashboardController.php
@@ -81,6 +81,7 @@ class DashboardController extends BaseController
             'onlyPodcastId'  => $onlyPodcastId,
         ];
 
+        $this->setHtmlHead(lang('Dashboard.home'));
         return view('dashboard', $data);
     }
 }
diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php
index 52a91d84e87b9aa6b169aae01c08355a8eb8ff13..ffa82b318cfc10d77e3e362cb9b4bc73138b121d 100644
--- a/modules/Admin/Controllers/EpisodeController.php
+++ b/modules/Admin/Controllers/EpisodeController.php
@@ -115,6 +115,7 @@ class EpisodeController extends BaseController
             'query'    => $query,
         ];
 
+        $this->setHtmlHead(lang('Episode.all_podcast_episodes'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -128,6 +129,7 @@ class EpisodeController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead($this->episode->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -148,6 +150,8 @@ class EpisodeController extends BaseController
                 $currentSeasonNumber
             ),
         ];
+
+        $this->setHtmlHead(lang('Episode.create'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -261,6 +265,7 @@ class EpisodeController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead(lang('Episode.edit'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -409,6 +414,7 @@ class EpisodeController extends BaseController
                 'episode' => $this->episode,
             ];
 
+            $this->setHtmlHead(lang('Episode.publish'));
             replace_breadcrumb_params([
                 0 => $this->podcast->at_handle,
                 1 => $this->episode->title,
@@ -521,6 +527,7 @@ class EpisodeController extends BaseController
                     ->first(),
             ];
 
+            $this->setHtmlHead(lang('Episode.publish_edit'));
             replace_breadcrumb_params([
                 0 => $this->podcast->at_handle,
                 1 => $this->episode->title,
@@ -670,11 +677,11 @@ class EpisodeController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead(lang('Episode.publish_date_edit'));
         replace_breadcrumb_params([
             0 => $this->podcast->title,
             1 => $this->episode->title,
         ]);
-
         return view('episode/publish_date_edit', $data);
     }
 
@@ -746,6 +753,7 @@ class EpisodeController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead(lang('Episode.unpublish'));
         replace_breadcrumb_params([
             0 => $this->podcast->title,
             1 => $this->episode->title,
@@ -822,6 +830,7 @@ class EpisodeController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead(lang('Episode.delete'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -928,6 +937,7 @@ class EpisodeController extends BaseController
             'themes'  => EpisodeModel::$themes,
         ];
 
+        $this->setHtmlHead(lang('Episode.embed.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
diff --git a/modules/Admin/Controllers/EpisodePersonController.php b/modules/Admin/Controllers/EpisodePersonController.php
index 8a6bcae5b7f10be52c0943d1734fe9c86479348d..a60df018bf74a017025e07656032e71b1472b1e2 100644
--- a/modules/Admin/Controllers/EpisodePersonController.php
+++ b/modules/Admin/Controllers/EpisodePersonController.php
@@ -58,6 +58,8 @@ class EpisodePersonController extends BaseController
             'personOptions'   => (new PersonModel())->getPersonOptions(),
             'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
         ];
+
+        $this->setHtmlHead(lang('Person.episode_form.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
diff --git a/modules/Admin/Controllers/FediverseController.php b/modules/Admin/Controllers/FediverseController.php
index 889c995895ed05896a298691ce108e6ecb8f0f64..a19545e535680ad800bfa870526b668959496400 100644
--- a/modules/Admin/Controllers/FediverseController.php
+++ b/modules/Admin/Controllers/FediverseController.php
@@ -26,6 +26,7 @@ class FediverseController extends BaseController
         $blockedActors = model('ActorModel', false)
             ->getBlockedActors();
 
+        $this->setHtmlHead(lang('Fediverse.blocked_actors'));
         return view('fediverse/blocked_actors', [
             'blockedActors' => $blockedActors,
         ]);
@@ -38,6 +39,7 @@ class FediverseController extends BaseController
         $blockedDomains = model('BlockedDomainModel', false)
             ->getBlockedDomains();
 
+        $this->setHtmlHead(lang('Fediverse.blocked_domains'));
         return view('fediverse/blocked_domains', [
             'blockedDomains' => $blockedDomains,
         ]);
diff --git a/modules/Admin/Controllers/NotificationController.php b/modules/Admin/Controllers/NotificationController.php
index 33c7d9ac65137dca364a14ac12ac8209804e5ea2..16c755f44b5c6277a52b7d3174e3066fbec70aa9 100644
--- a/modules/Admin/Controllers/NotificationController.php
+++ b/modules/Admin/Controllers/NotificationController.php
@@ -66,10 +66,10 @@ class NotificationController extends BaseController
             'pager'         => $notifications->pager,
         ];
 
+        $this->setHtmlHead(lang('Notifications.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
-
         return view('podcast/notifications', $data);
     }
 
diff --git a/modules/Admin/Controllers/PageController.php b/modules/Admin/Controllers/PageController.php
index f23bf2428233ac7640da56a31e9285969e19830d..f2df5e6230247fe335e2d31488319239bd1a3df0 100644
--- a/modules/Admin/Controllers/PageController.php
+++ b/modules/Admin/Controllers/PageController.php
@@ -34,6 +34,7 @@ class PageController extends BaseController
 
     public function list(): string
     {
+        $this->setHtmlHead(lang('Page.all_pages'));
         $data = [
             'pages' => (new PageModel())->findAll(),
         ];
@@ -43,6 +44,7 @@ class PageController extends BaseController
 
     public function view(): string
     {
+        $this->setHtmlHead($this->page->title);
         return view('page/view', [
             'page' => $this->page,
         ]);
@@ -52,6 +54,7 @@ class PageController extends BaseController
     {
         helper('form');
 
+        $this->setHtmlHead(lang('Page.create'));
         return view('page/create');
     }
 
@@ -83,6 +86,7 @@ class PageController extends BaseController
     {
         helper('form');
 
+        $this->setHtmlHead(lang('Page.edit'));
         replace_breadcrumb_params([
             0 => $this->page->title,
         ]);
diff --git a/modules/Admin/Controllers/PersonController.php b/modules/Admin/Controllers/PersonController.php
index 85b466c8559c2c2bc734adcd6bd3bcc2b63759a2..7f37e4ab4d0652d7432f35350d70d0a1145ad65f 100644
--- a/modules/Admin/Controllers/PersonController.php
+++ b/modules/Admin/Controllers/PersonController.php
@@ -42,6 +42,7 @@ class PersonController extends BaseController
                 ->findAll(),
         ];
 
+        $this->setHtmlHead(lang('Person.all_persons'));
         return view('person/list', $data);
     }
 
@@ -51,6 +52,7 @@ class PersonController extends BaseController
             'person' => $this->person,
         ];
 
+        $this->setHtmlHead($this->person->full_name);
         replace_breadcrumb_params([
             0 => $this->person->full_name,
         ]);
@@ -61,6 +63,7 @@ class PersonController extends BaseController
     {
         helper(['form']);
 
+        $this->setHtmlHead(lang('Person.create'));
         return view('person/create');
     }
 
@@ -112,6 +115,7 @@ class PersonController extends BaseController
             'person' => $this->person,
         ];
 
+        $this->setHtmlHead(lang('Person.edit'));
         replace_breadcrumb_params([
             0 => $this->person->full_name,
         ]);
diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php
index 9130d065dfc10311214c688ab0c6794246c0c28d..5075a26f1ff0fe5641df8ef79d38d765abc512a8 100644
--- a/modules/Admin/Controllers/PodcastController.php
+++ b/modules/Admin/Controllers/PodcastController.php
@@ -68,6 +68,7 @@ class PodcastController extends BaseController
             ];
         }
 
+        $this->setHtmlHead(lang('Podcast.all_podcasts'));
         return view('podcast/list', $data);
     }
 
@@ -77,6 +78,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -89,6 +91,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -101,6 +104,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -113,6 +117,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -125,6 +130,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -137,6 +143,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -149,6 +156,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -161,6 +169,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead($this->podcast->title);
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -180,6 +189,7 @@ class PodcastController extends BaseController
             'browserLang'     => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
         ];
 
+        $this->setHtmlHead(lang('Podcast.create'));
         return view('podcast/create', $data);
     }
 
@@ -270,6 +280,7 @@ class PodcastController extends BaseController
             'categoryOptions' => $categoryOptions,
         ];
 
+        $this->setHtmlHead(lang('Podcast.edit'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -410,6 +421,7 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead(lang('Podcast.delete'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -578,10 +590,10 @@ class PodcastController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead(lang('Podcast.publish'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
-
         return view('podcast/publish', $data);
     }
 
@@ -713,10 +725,10 @@ class PodcastController extends BaseController
                 ->first(),
         ];
 
+        $this->setHtmlHead(lang('Podcast.publish_edit'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
-
         return view('podcast/publish_edit', $data);
     }
 
diff --git a/modules/Admin/Controllers/PodcastPersonController.php b/modules/Admin/Controllers/PodcastPersonController.php
index ec02c3934cc01a8fa0a68324030ba786df643489..bdb03a4b96304f379b80133d22166b280d8fcbfd 100644
--- a/modules/Admin/Controllers/PodcastPersonController.php
+++ b/modules/Admin/Controllers/PodcastPersonController.php
@@ -46,6 +46,8 @@ class PodcastPersonController extends BaseController
             'personOptions'   => (new PersonModel())->getPersonOptions(),
             'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
         ];
+
+        $this->setHtmlHead(lang('Person.podcast_form.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php
index 0ba695091ea4dc0d363459c57d736db28605ef53..0262b3fbcfdcb0160d38a0744fa1236bc233cb38 100644
--- a/modules/Admin/Controllers/SettingsController.php
+++ b/modules/Admin/Controllers/SettingsController.php
@@ -30,6 +30,7 @@ class SettingsController extends BaseController
     public function index(): string
     {
         helper('form');
+        $this->setHtmlHead(lang('Settings.title'));
         return view('settings/general');
     }
 
@@ -202,6 +203,7 @@ class SettingsController extends BaseController
     public function theme(): string
     {
         helper('form');
+        $this->setHtmlHead(lang('Settings.theme.title'));
         return view('settings/theme');
     }
 
diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php
index ca96f56b3038ee0ea18e54770446a460dc5208ad..1d6f5ae94057c1c318dd6574b4541daf75c3c5c6 100644
--- a/modules/Admin/Controllers/SoundbiteController.php
+++ b/modules/Admin/Controllers/SoundbiteController.php
@@ -76,6 +76,7 @@ class SoundbiteController extends BaseController
             'pager'      => $soundbitesBuilder->pager,
         ];
 
+        $this->setHtmlHead(lang('Soundbite.list.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -92,6 +93,7 @@ class SoundbiteController extends BaseController
             'episode' => $this->episode,
         ];
 
+        $this->setHtmlHead(lang('Soundbite.form.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php
index 7bf1d0d1d5062b58936c5ea8d36d4b1142aaa24f..848cee8dd3d67d71b082cb3375e1209cb52adeda 100644
--- a/modules/Admin/Controllers/VideoClipsController.php
+++ b/modules/Admin/Controllers/VideoClipsController.php
@@ -82,6 +82,7 @@ class VideoClipsController extends BaseController
             'pager'      => $videoClipsBuilder->pager,
         ];
 
+        $this->setHtmlHead(lang('VideoClip.list.title'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -99,6 +100,9 @@ class VideoClipsController extends BaseController
             'videoClip' => $videoClip,
         ];
 
+        $this->setHtmlHead(lang('VideoClip.title', [
+            'videoClipLabel' => esc($videoClip->title),
+        ]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->episode->title,
@@ -128,9 +132,10 @@ class VideoClipsController extends BaseController
             'transcript' => $this->episode->transcript instanceof Transcript,
         ];
 
+        $this->setHtmlHead(lang('VideoClip.form.title'));
+
         if (in_array(false, $checks, true)) {
             $data['checks'] = $checks;
-
             return view('episode/video_clips_requirements', $data);
         }
 
diff --git a/modules/Analytics/Config/Routes.php b/modules/Analytics/Config/Routes.php
index d24e716fddebf3a4f86c78a1661115b45fd361e5..9097151ce8ed64e417c69ad84d2e673957304d6e 100644
--- a/modules/Analytics/Config/Routes.php
+++ b/modules/Analytics/Config/Routes.php
@@ -2,15 +2,13 @@
 
 declare(strict_types=1);
 
-use CodeIgniter\Router\RouteCollection;
-
 /**
  * @copyright  2021 Ad Aures
  * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
  * @link       https://castopod.org/
  */
 
-/** @var RouteCollection $routes */
+/** @var \CodeIgniter\Router\RouteCollection $routes */
 
 /**
  * Analytics routes file
diff --git a/modules/Analytics/Entities/AnalyticsPodcasts.php b/modules/Analytics/Entities/AnalyticsPodcasts.php
index 3ff2b58ec79a86c70ef7cea6e8ece3a5092b3081..45d4b62476839b4801a61a136f056a1cb7172857 100644
--- a/modules/Analytics/Entities/AnalyticsPodcasts.php
+++ b/modules/Analytics/Entities/AnalyticsPodcasts.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php b/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php
index d9dd478ad6e8883a3b5e4577076bc9f843bea474..59ca02eb71d4d924fafab63bd837df05bc46dc74 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php b/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php
index 354d98ec53901b540ca95ea1912cefb83ab2cf22..1aa5959ae27fba7352f362c5c8f96e316a01753f 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByHour.php b/modules/Analytics/Entities/AnalyticsPodcastsByHour.php
index e74ef64af337e3a07c40653120e01b2912c4830b..bfc33b044028ae03467f0cac5f6877b78538b379 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByHour.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByHour.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php b/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php
index 6f2b50441127c4fad36f0111960359a0d4a368fb..8fb0acf7e0c7ecaffdc35057e65b9cce8dbad7c2 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php b/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php
index 777c41486730c005e1a6d1238dbfe6a377b84abb..684667f3f5607849d98f4514c6d449406f1df132 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByService.php b/modules/Analytics/Entities/AnalyticsPodcastsByService.php
index f86d6db60f05fe12da818c863cd0c5be7c0acdb3..fb8a03666752e527e690b2891d216cf0978db65b 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsByService.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsByService.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 use Opawg\UserAgentsV2Php\UserAgentsRSS;
 
 /**
diff --git a/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php b/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php
index f048fea0015674cdf328b45302f5793ec24aa7bf..09974b76936ddc446b60fd89ba86329b4b74511e 100644
--- a/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php
+++ b/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php
@@ -11,6 +11,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php b/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php
index 1c897d7ca6aef3a661c6d5f7d76e6a2df13d6944..016a3d0a57362a3f72b6c5602bc87e15b332badb 100644
--- a/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php
+++ b/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $id
diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php b/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php
index 80c568c8b248be0d4d8c2a53d5917615b3506e60..9d764f7e96309c6286580931ad314e3c8ff295ad 100644
--- a/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php
+++ b/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php b/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php
index 51d436b6270711a26f5b3c4b0d089197182195bc..7e8294c2201b216e3ec6f6be56357fa89efc6dae 100644
--- a/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php
+++ b/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php b/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php
index 15819f739a2f2524789eec6c09aeaf36ed7645a6..c0edf9c8512ef8389699069f3cb7028c56fe6e02 100644
--- a/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php
+++ b/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 namespace Modules\Analytics\Entities;
 
 use CodeIgniter\Entity\Entity;
+use CodeIgniter\I18n\Time;
 
 /**
  * @property int $podcast_id
diff --git a/modules/Analytics/Helpers/analytics_helper.php b/modules/Analytics/Helpers/analytics_helper.php
index 99a21ed9ca5fabc9334f9c3f40dfd9f9a18765c1..55fe2876805d4fcbcf6c244f4085fdc6e1dc44da 100644
--- a/modules/Analytics/Helpers/analytics_helper.php
+++ b/modules/Analytics/Helpers/analytics_helper.php
@@ -286,8 +286,7 @@ if (! function_exists('podcast_hit')) {
                         $parts = explode('-', $range);
                         $downloadedBytes += array_key_exists(1, $parts)
                             ? $fileSize
-                            : (int) $parts[1] -
-                                (array_key_exists(0, $parts) ? 0 : (int) $parts[0]);
+                            : (int) $parts[1] - (int) $parts[0];
                     }
                 }
 
diff --git a/modules/Api/Rest/V1/Controllers/EpisodeController.php b/modules/Api/Rest/V1/Controllers/EpisodeController.php
index 342b81d70e8a5c405c52c3f56b412b82b76e2653..d992b530e25e5e9c6e2422f6d1db1b487fb5b8e2 100644
--- a/modules/Api/Rest/V1/Controllers/EpisodeController.php
+++ b/modules/Api/Rest/V1/Controllers/EpisodeController.php
@@ -71,7 +71,7 @@ class EpisodeController extends Controller
     protected static function mapEpisode(Episode $episode): Episode
     {
         $episode->cover_url = $episode->getCover()
-->file_url;
+            ->file_url;
         $episode->duration = round($episode->audio->duration);
 
         return $episode;
diff --git a/modules/Api/Rest/V1/Controllers/PodcastController.php b/modules/Api/Rest/V1/Controllers/PodcastController.php
index 98fa68b8bf98116b4167db48d23806ef7ee01b4a..cce5178379c9bb642e2258104fb570c7278a08f4 100644
--- a/modules/Api/Rest/V1/Controllers/PodcastController.php
+++ b/modules/Api/Rest/V1/Controllers/PodcastController.php
@@ -44,9 +44,9 @@ class PodcastController extends Controller
     {
         $podcast->feed_url = $podcast->getFeedUrl();
         $podcast->actor_display_name = $podcast->getActor()
-->display_name;
+            ->display_name;
         $podcast->cover_url = $podcast->getCover()
-->file_url;
+            ->file_url;
 
         $categories = [$podcast->getCategory(), ...$podcast->getOtherCategories()];
 
diff --git a/modules/Auth/Controllers/ContributorController.php b/modules/Auth/Controllers/ContributorController.php
index 4326a750007e24ebd88300549fdfa27e631272f3..2c5c796144f8625b99e3d807e24f3101a7a6b3c4 100644
--- a/modules/Auth/Controllers/ContributorController.php
+++ b/modules/Auth/Controllers/ContributorController.php
@@ -56,6 +56,7 @@ class ContributorController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead(lang('Contributor.podcast_contributors'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -69,6 +70,10 @@ class ContributorController extends BaseController
             'contributor' => (new UserModel())->getPodcastContributor($this->contributor->id, $this->podcast->id),
         ];
 
+        $this->setHtmlHead(lang('Contributor.view', [
+            'username'     => esc($this->contributor->username),
+            'podcastTitle' => esc($this->podcast->title),
+        ]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->contributor->username,
@@ -113,6 +118,7 @@ class ContributorController extends BaseController
             'roleOptions'        => $roleOptions,
         ];
 
+        $this->setHtmlHead(lang('Contributor.add_contributor', [esc($this->podcast->title)]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -170,6 +176,7 @@ class ContributorController extends BaseController
             'roleOptions'      => $roleOptions,
         ];
 
+        $this->setHtmlHead(lang('Contributor.edit_role', [esc($this->contributor->username)]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->contributor->username,
@@ -208,6 +215,9 @@ class ContributorController extends BaseController
             'contributor' => $this->contributor,
         ];
 
+        $this->setHtmlHead(lang('Contributor.delete_form.title', [
+            'contributor' => $this->contributor->username,
+        ]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => $this->contributor->username,
diff --git a/modules/Auth/Controllers/MyAccountController.php b/modules/Auth/Controllers/MyAccountController.php
index 87cd97b114de63008b47418c4327aaad20caba5f..9c7fef2d0218d408382bc3f5a754a22ced30d9bb 100644
--- a/modules/Auth/Controllers/MyAccountController.php
+++ b/modules/Auth/Controllers/MyAccountController.php
@@ -18,6 +18,7 @@ class MyAccountController extends BaseController
 {
     public function index(): string
     {
+        $this->setHtmlHead(lang('MyAccount.info'));
         return view('my_account/view');
     }
 
@@ -25,6 +26,7 @@ class MyAccountController extends BaseController
     {
         helper('form');
 
+        $this->setHtmlHead(lang('MyAccount.changePassword'));
         return view('my_account/change_password');
     }
 
diff --git a/modules/Auth/Controllers/UserController.php b/modules/Auth/Controllers/UserController.php
index 98a07f3f335c459d9267aee22f575b05bc5e235f..dd242f41c371631bb45636d16624af80f2e40291 100644
--- a/modules/Auth/Controllers/UserController.php
+++ b/modules/Auth/Controllers/UserController.php
@@ -42,6 +42,7 @@ class UserController extends BaseController
             'users' => (new UserModel())->findAll(),
         ];
 
+        $this->setHtmlHead(lang('User.all_users'));
         return view('user/list', $data);
     }
 
@@ -51,6 +52,9 @@ class UserController extends BaseController
             'user' => $this->user,
         ];
 
+        $this->setHtmlHead(lang('User.view', [
+            'username' => esc($this->user->username),
+        ]));
         replace_breadcrumb_params([
             0 => $this->user->username,
         ]);
@@ -76,6 +80,7 @@ class UserController extends BaseController
             'roleOptions' => $roleOptions,
         ];
 
+        $this->setHtmlHead(lang('User.create'));
         return view('user/create', $data);
     }
 
@@ -182,6 +187,9 @@ class UserController extends BaseController
             'roleOptions' => $roleOptions,
         ];
 
+        $this->setHtmlHead(lang('User.edit_role', [
+            'username' => esc($this->user->username),
+        ]));
         replace_breadcrumb_params([
             0 => $this->user->username,
         ]);
@@ -221,6 +229,9 @@ class UserController extends BaseController
             'user' => $this->user,
         ];
 
+        $this->setHtmlHead(lang('User.delete_form.title', [
+            'user' => $this->user->username,
+        ]));
         replace_breadcrumb_params([
             0 => $this->user->username,
         ]);
diff --git a/modules/Fediverse/Entities/Activity.php b/modules/Fediverse/Entities/Activity.php
index ba017f16f3cec1be5601e6593ad569f8773746cc..0865ff45c3b37140aff857ed48ec12b61d2c8e43 100644
--- a/modules/Fediverse/Entities/Activity.php
+++ b/modules/Fediverse/Entities/Activity.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
 
 namespace Modules\Fediverse\Entities;
 
+use CodeIgniter\I18n\Time;
 use Michalsn\Uuid\UuidEntity;
 use RuntimeException;
 
diff --git a/modules/Fediverse/Helpers/fediverse_helper.php b/modules/Fediverse/Helpers/fediverse_helper.php
index 8877afd576197b9e5a52a5f2e743410f9248ff0e..d73bbe14e0ce28f9e23872f6bff6317ed6dfb361 100644
--- a/modules/Fediverse/Helpers/fediverse_helper.php
+++ b/modules/Fediverse/Helpers/fediverse_helper.php
@@ -41,7 +41,7 @@ if (! function_exists('split_handle')) {
     /**
      * Splits handle into its parts (username, host and port)
      *
-     * @return array<string, string>|false
+     * @return array{0:string,username:non-empty-string,1:non-empty-string,domain:non-empty-string,2:non-empty-string,port?:non-falsy-string,3?:non-falsy-string}
      */
     function split_handle(string $handle): array | false
     {
diff --git a/modules/Fediverse/WebFinger.php b/modules/Fediverse/WebFinger.php
index 89af5d84ccc9e5c0c937bf3dfb5d75a1b9cc1a36..f8d0b8c86b4ff3f44d6d699b979cb37dd712dde9 100644
--- a/modules/Fediverse/WebFinger.php
+++ b/modules/Fediverse/WebFinger.php
@@ -101,7 +101,7 @@ class WebFinger
     /**
      * Split resource into its parts (username, domain)
      *
-     * @return array<string, string>|false
+     * @return array{0:string,username:non-empty-string,1:non-empty-string,2:non-empty-string,domain:non-falsy-string,3:non-falsy-string,4:non-falsy-string,5?:non-falsy-string}
      */
     private function splitResource(string $resource): bool|array
     {
diff --git a/modules/Media/Entities/BaseMedia.php b/modules/Media/Entities/BaseMedia.php
index 206ad11531c68f557baf69832dff3602753c31b8..392a9556dc1ffda917658e642143c75465b7dbbb 100644
--- a/modules/Media/Entities/BaseMedia.php
+++ b/modules/Media/Entities/BaseMedia.php
@@ -25,7 +25,7 @@ use RuntimeException;
  * @property string $file_extension
  * @property int $file_size
  * @property string $file_mimetype
- * @property array|null $file_metadata
+ * @property array<mixed>|null $file_metadata
  * @property 'image'|'audio'|'video'|'document' $type
  * @property string|null $description
  * @property string|null $language_code
diff --git a/modules/Media/Entities/Image.php b/modules/Media/Entities/Image.php
index 586eaad2be6f57802f45c87c3b0bc089c9eea982..564823bccfda01f1864384e2fcd00bacd0778902 100644
--- a/modules/Media/Entities/Image.php
+++ b/modules/Media/Entities/Image.php
@@ -16,7 +16,7 @@ use GdImage;
 use Override;
 
 /**
- * @property array $sizes
+ * @property array<string, array<string, int|string>> $sizes
  */
 class Image extends BaseMedia
 {
diff --git a/modules/Media/Helpers/media_helper.php b/modules/Media/Helpers/media_helper.php
index 6ace28c49b38983eec52a6aa6bd1330a31490f36..85bad2534da82a4e391a9afa91e76e42714e68f8 100644
--- a/modules/Media/Helpers/media_helper.php
+++ b/modules/Media/Helpers/media_helper.php
@@ -34,7 +34,7 @@ if (! function_exists('download_file')) {
         curl_setopt($ch, CURLOPT_HTTPHEADER, ['User-Agent: Castopod/' . CP_VERSION]);
 
         // follow redirects up to 20, like Apple Podcasts
-        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
         curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
 
         curl_exec($ch);
diff --git a/modules/Platforms/Controllers/PlatformController.php b/modules/Platforms/Controllers/PlatformController.php
index ce4cd42e1620c662ff3275e8af2d3374050940a1..b104ec7adfe0adae5c8b60ecb12aa9afb97d9c68 100644
--- a/modules/Platforms/Controllers/PlatformController.php
+++ b/modules/Platforms/Controllers/PlatformController.php
@@ -55,10 +55,10 @@ class PlatformController extends BaseController
             'platforms'    => (new PlatformModel())->getPlatformsWithData($this->podcast->id, $platformType),
         ];
 
+        $this->setHtmlHead(lang("Platforms.title.{$platformType}"));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
-
         return view('podcast/platforms', $data);
     }
 
diff --git a/modules/Platforms/Models/PlatformModel.php b/modules/Platforms/Models/PlatformModel.php
index 290a90f813e5a88d82ec62cad502f3f9f1c89245..634255c61e81194b148f93fd646deb8eab9681b6 100644
--- a/modules/Platforms/Models/PlatformModel.php
+++ b/modules/Platforms/Models/PlatformModel.php
@@ -127,7 +127,7 @@ class PlatformModel extends Model
     }
 
     /**
-     * @param array<array<string, string|int>> $data
+     * @param array<array<string, bool|int|string|null>> $data
      *
      * @return int|false Number of rows inserted or FALSE on failure
      */
diff --git a/modules/Platforms/Platforms.php b/modules/Platforms/Platforms.php
index d5cd26e232af603e0925c45fa520123b9cad43cf..019f6847fcdce197974bc5f8e4732bb0940746ac 100644
--- a/modules/Platforms/Platforms.php
+++ b/modules/Platforms/Platforms.php
@@ -458,7 +458,7 @@ class Platforms
     }
 
     /**
-     * @return null|array{label:string,home_url:string,submit_url:?string}>
+     * @return null|array{label:string,home_url:string,submit_url:?string}
      */
     public function findPlatformBySlug(string $type, string $slug): ?array
     {
diff --git a/modules/Plugins/Commands/CreatePlugin.php b/modules/Plugins/Commands/CreatePlugin.php
index a9b31a8011308e233dbdb31bd1a1044c9167bf67..d81807f866badb35928563326fed73a0714379df 100644
--- a/modules/Plugins/Commands/CreatePlugin.php
+++ b/modules/Plugins/Commands/CreatePlugin.php
@@ -16,10 +16,10 @@ class CreatePlugin extends BaseCommand
 {
     protected const HOOKS_IMPORTS = [
         'rssBeforeChannel' => ['use App\Entities\Podcast;'],
-        'rssAfterChannel'  => ['use App\Entities\Podcast;', 'use App\Libraries\SimpleRSSElement;'],
+        'rssAfterChannel'  => ['use App\Entities\Podcast;', 'use App\Libraries\RssFeed;'],
         'rssBeforeItem'    => ['use App\Entities\Episode;'],
-        'rssAfterItem'     => ['use App\Entities\Episode;', 'use App\Libraries\SimpleRSSElement;'],
-        'siteHead'         => [],
+        'rssAfterItem'     => ['use App\Entities\Episode;', 'use App\Libraries\RssFeed;'],
+        'siteHead'         => ['use use App\Libraries\HtmlHead'],
     ];
 
     protected const HOOKS_METHODS = [
@@ -27,7 +27,7 @@ class CreatePlugin extends BaseCommand
     {
         // YOUR CODE HERE
     }',
-        'rssAfterChannel' => '    public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
+        'rssAfterChannel' => '    public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void
     {
         // YOUR CODE HERE
     }',
@@ -35,11 +35,11 @@ class CreatePlugin extends BaseCommand
     {
         // YOUR CODE HERE
     }',
-        'rssAfterItem' => '    public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
+        'rssAfterItem' => '    public function rssAfterItem(Episode $episode, RssFeed $item): void
     {
         // YOUR CODE HERE
     }',
-        'siteHead' => '    public function siteHead(): void
+        'siteHead' => '    public function siteHead(HtmlHead $head): void
     {
         // YOUR CODE HERE
     }',
diff --git a/modules/Plugins/Config/Routes.php b/modules/Plugins/Config/Routes.php
index e7785c9919ee95fd12c9e071a8ca18d777d6a01c..81e2dc27bac71e3000ce14a074702de007867759 100644
--- a/modules/Plugins/Config/Routes.php
+++ b/modules/Plugins/Config/Routes.php
@@ -8,7 +8,7 @@ use CodeIgniter\Router\RouteCollection;
 
 $routes->group(
     config('Admin')
-->gateway,
+        ->gateway,
     [
         'namespace' => 'Modules\Plugins\Controllers',
     ],
diff --git a/modules/Plugins/Controllers/PluginController.php b/modules/Plugins/Controllers/PluginController.php
index 658d04951b5dc52da227bd57d3b8d9bf8fd5a3f0..8f2b145cb01537ace19a1be1e4baabd572a067be 100644
--- a/modules/Plugins/Controllers/PluginController.php
+++ b/modules/Plugins/Controllers/PluginController.php
@@ -38,6 +38,7 @@ class PluginController extends BaseController
 
         $pager_links = $pager->makeLinks($page, $perPage, $total);
 
+        $this->setHtmlHead(lang('Plugins.installed'));
         return view('plugins/installed', [
             'total'       => $total,
             'plugins'     => $this->plugins->getPlugins($page, $perPage),
@@ -47,8 +48,9 @@ class PluginController extends BaseController
 
     public function vendor(string $vendor): string
     {
-
         $vendorPlugins = $this->plugins->getVendorPlugins($vendor);
+
+        $this->setHtmlHead(lang('Plugins.installed'));
         replace_breadcrumb_params([
             $vendor => $vendor,
         ]);
@@ -68,6 +70,7 @@ class PluginController extends BaseController
             throw PageNotFoundException::forPageNotFound();
         }
 
+        $this->setHtmlHead($plugin->getTitle());
         replace_breadcrumb_params([
             $vendor  => $vendor,
             $package => $package,
@@ -137,6 +140,10 @@ class PluginController extends BaseController
         $data['fields'] = $fields;
 
         helper('form');
+        $this->setHtmlHead(lang('Plugins.settingsTitle', [
+            'pluginTitle' => $plugin->getTitle(),
+            'type'        => $type,
+        ]));
         replace_breadcrumb_params($breadcrumbReplacements);
         return view('plugins/settings', $data);
     }
diff --git a/modules/Plugins/Core/BasePlugin.php b/modules/Plugins/Core/BasePlugin.php
index 9df37dc306ab755cef7c8442aa521a5bfae1e743..6257b382763174b2e6b23721f5d70c58538e3140 100644
--- a/modules/Plugins/Core/BasePlugin.php
+++ b/modules/Plugins/Core/BasePlugin.php
@@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
 
 use App\Entities\Episode;
 use App\Entities\Podcast;
+use App\Libraries\HtmlHead;
 use App\Libraries\RssFeed;
 use CodeIgniter\HTTP\URI;
 use League\CommonMark\Environment\Environment;
@@ -101,7 +102,7 @@ abstract class BasePlugin implements PluginInterface
     }
 
     #[Override]
-    public function siteHead(): void
+    public function siteHead(HtmlHead $head): void
     {
     }
 
@@ -264,7 +265,7 @@ abstract class BasePlugin implements PluginInterface
         return $title;
     }
 
-    final public function getDescription(): ?string
+    final public function getDescription(): string
     {
         $key = sprintf('Plugin.%s.description', $this->key);
 
diff --git a/modules/Plugins/Core/PluginInterface.php b/modules/Plugins/Core/PluginInterface.php
index c8b4f73e8f30a3070a805d9d07f00b45c6452093..bd30aad5538ca9bb418a7c556dd45bc577e7135c 100644
--- a/modules/Plugins/Core/PluginInterface.php
+++ b/modules/Plugins/Core/PluginInterface.php
@@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
 
 use App\Entities\Episode;
 use App\Entities\Podcast;
+use App\Libraries\HtmlHead;
 use App\Libraries\RssFeed;
 
 interface PluginInterface
@@ -18,5 +19,5 @@ interface PluginInterface
 
     public function rssAfterItem(Episode $episode, RssFeed $item): void;
 
-    public function siteHead(): void;
+    public function siteHead(HtmlHead $head): void;
 }
diff --git a/modules/Plugins/Core/Plugins.php b/modules/Plugins/Core/Plugins.php
index ab24a87ff879d8a62a159712f7166e13be4b3a00..95f7b3d78fe7cd60fd88ed88c9a7f9b8ade4ea7e 100644
--- a/modules/Plugins/Core/Plugins.php
+++ b/modules/Plugins/Core/Plugins.php
@@ -6,6 +6,7 @@ namespace Modules\Plugins\Core;
 
 use App\Entities\Episode;
 use App\Entities\Podcast;
+use App\Libraries\HtmlHead;
 use App\Libraries\RssFeed;
 use Config\Database;
 use Modules\Plugins\Config\Plugins as PluginsConfig;
@@ -15,7 +16,7 @@ use Modules\Plugins\Config\Plugins as PluginsConfig;
  * @method void rssAfterChannel(Podcast $podcast, RssFeed $channel)
  * @method void rssBeforeItem(Episode $episode)
  * @method void rssAfterItem(Episode $episode, RssFeed $item)
- * @method void siteHead()
+ * @method void siteHead(HtmlHead $head)
  */
 class Plugins
 {
@@ -29,6 +30,7 @@ class Plugins
         'datetime'        => ['valid_date[Y-m-d H:i]'],
         'email'           => ['valid_email'],
         'group'           => ['permit_empty', 'is_list'],
+        'html'            => ['string'],
         'markdown'        => ['string'],
         'number'          => ['integer'],
         'radio-group'     => ['string'],
diff --git a/modules/Plugins/Manifest/Field.php b/modules/Plugins/Manifest/Field.php
index 4ccb735fb665defe386827ec940cecf87f23585c..1d0d16c226e20f652d1e71e993e9985608d2ad52 100644
--- a/modules/Plugins/Manifest/Field.php
+++ b/modules/Plugins/Manifest/Field.php
@@ -7,7 +7,7 @@ namespace Modules\Plugins\Manifest;
 use Override;
 
 /**
- * @property 'checkbox'|'datetime'|'email'|'group'|'markdown'|'number'|'radio-group'|'rss'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url' $type
+ * @property 'checkbox'|'datetime'|'email'|'group'|'html'|'markdown'|'number'|'radio-group'|'rss'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url' $type
  * @property string $key
  * @property string $label
  * @property string $hint
@@ -20,7 +20,7 @@ use Override;
 class Field extends ManifestObject
 {
     protected const VALIDATION_RULES = [
-        'type'     => 'permit_empty|in_list[checkbox,datetime,email,group,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]',
+        'type'     => 'permit_empty|in_list[checkbox,datetime,email,group,html,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]',
         'key'      => 'required|alpha_dash',
         'label'    => 'required|string',
         'hint'     => 'permit_empty|string',
diff --git a/modules/Plugins/Manifest/manifest.schema.json b/modules/Plugins/Manifest/manifest.schema.json
index f08cb60277cc2b9d05bf3fd650cb4abd3431cf7f..df5d9edf311b56b6d2f0dea915dcbc8222c6ce44 100644
--- a/modules/Plugins/Manifest/manifest.schema.json
+++ b/modules/Plugins/Manifest/manifest.schema.json
@@ -178,6 +178,7 @@
             "datetime",
             "email",
             "group",
+            "html",
             "markdown",
             "number",
             "radio-group",
diff --git a/modules/PodcastImport/Commands/PodcastImport.php b/modules/PodcastImport/Commands/PodcastImport.php
index 4985faf073a578795a07a67fded3c19c78bdd3fc..cf5bc8483d717d97bb0f52e59da20bfbccae0de4 100644
--- a/modules/PodcastImport/Commands/PodcastImport.php
+++ b/modules/PodcastImport/Commands/PodcastImport.php
@@ -160,7 +160,7 @@ class PodcastImport extends BaseCommand
 
             $podcastModel = new PodcastModel();
             if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
-                throw new Exception((string) print_r($podcastModel->errors()));
+                throw new Exception(print_r($podcastModel->errors(), true));
             }
 
             CLI::showProgress(false);
@@ -260,7 +260,7 @@ class PodcastImport extends BaseCommand
         $podcastModel = new PodcastModel();
         if (! ($podcastId = $podcastModel->insert($podcast, true))) {
             $db->transRollback();
-            throw new Exception((string) print_r($podcastModel->errors()));
+            throw new Exception(print_r($podcastModel->errors(), true));
         }
 
         $podcast->id = $podcastId;
@@ -326,7 +326,7 @@ class PodcastImport extends BaseCommand
                 ]);
 
                 if (! $newPersonId = $personModel->insert($newPodcastPerson)) {
-                    throw new Exception((string) print_r($personModel->errors()));
+                    throw new Exception(print_r($personModel->errors(), true));
                 }
             }
 
@@ -353,7 +353,7 @@ class PodcastImport extends BaseCommand
                 $personGroupSlug,
                 $personRoleSlug
             )) {
-                throw new Exception((string) print_r($podcastPersonModel->errors()));
+                throw new Exception(print_r($podcastPersonModel->errors(), true));
             }
         }
 
@@ -498,7 +498,7 @@ class PodcastImport extends BaseCommand
 
             if (! ($episodeId = $episodeModel->insert($episode, true))) {
                 $db->transRollback();
-                throw new Exception((string) print_r($episodeModel->errors()));
+                throw new Exception(print_r($episodeModel->errors(), true));
             }
 
             $this->importEpisodePersons($episodeId, $item->podcast_persons);
@@ -546,7 +546,7 @@ class PodcastImport extends BaseCommand
                 ]);
 
                 if (! ($newPersonId = $personModel->insert($newPerson))) {
-                    throw new Exception((string) print_r($personModel->errors()));
+                    throw new Exception(print_r($personModel->errors(), true));
                 }
             }
 
@@ -574,7 +574,7 @@ class PodcastImport extends BaseCommand
                 $personGroupSlug,
                 $personRoleSlug
             )) {
-                throw new Exception((string) print_r($episodePersonModel->errors()));
+                throw new Exception(print_r($episodePersonModel->errors(), true));
             }
         }
     }
diff --git a/modules/PodcastImport/Controllers/PodcastImportController.php b/modules/PodcastImport/Controllers/PodcastImportController.php
index 3eb0f6522edba024fcfa84eece3bec9575d05cea..919dc47837cde21d1124885f2a6645fc8c498053 100644
--- a/modules/PodcastImport/Controllers/PodcastImportController.php
+++ b/modules/PodcastImport/Controllers/PodcastImportController.php
@@ -28,6 +28,7 @@ class PodcastImportController extends BaseController
     {
         helper('podcast_import');
 
+        $this->setHtmlHead(lang('Podcast.all_imports'));
         return view('import/queue', [
             'podcastImportsQueue' => get_import_tasks(),
         ]);
@@ -41,6 +42,7 @@ class PodcastImportController extends BaseController
 
         helper('podcast_import');
 
+        $this->setHtmlHead(lang('Podcast.all_imports'));
         replace_breadcrumb_params([
             0 => $podcast->at_handle,
         ]);
@@ -63,6 +65,7 @@ class PodcastImportController extends BaseController
             'browserLang'     => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
         ];
 
+        $this->setHtmlHead(lang('Podcast.import'));
         return view('import/add_to_queue', $data);
     }
 
@@ -112,6 +115,7 @@ class PodcastImportController extends BaseController
 
         helper('form');
 
+        $this->setHtmlHead(lang('PodcastImport.syncForm.title'));
         replace_breadcrumb_params([
             0 => $podcast->at_handle,
         ]);
diff --git a/modules/PodcastImport/Helpers/podcast_import_helper.php b/modules/PodcastImport/Helpers/podcast_import_helper.php
index 29c3acbb30a5432b145ccac97e4dc5a86a9183f9..29a8bda055698513b40e78e271733498acb93242 100644
--- a/modules/PodcastImport/Helpers/podcast_import_helper.php
+++ b/modules/PodcastImport/Helpers/podcast_import_helper.php
@@ -21,10 +21,6 @@ if (! function_exists('get_import_tasks')) {
         $podcastImportsQueue = service('settings')
             ->get('Import.queue') ?? [];
 
-        if (! is_array($podcastImportsQueue)) {
-            return [];
-        }
-
         if ($podcastHandle !== null) {
             $podcastImportsQueue = array_filter(
                 $podcastImportsQueue,
@@ -48,6 +44,6 @@ if (! function_exists('get_import_tasks')) {
             return $a->created_at->isAfter($b->created_at) ? -1 : 1;
         });
 
-        return array_values($podcastImportsQueue);
+        return $podcastImportsQueue;
     }
 }
diff --git a/modules/PremiumPodcasts/Controllers/SubscriptionController.php b/modules/PremiumPodcasts/Controllers/SubscriptionController.php
index afece7fdc72e997a53a35bc55030779812a2dc07..54182140575b3fb70ee6a0f812cbe42627d73ddb 100644
--- a/modules/PremiumPodcasts/Controllers/SubscriptionController.php
+++ b/modules/PremiumPodcasts/Controllers/SubscriptionController.php
@@ -59,6 +59,7 @@ class SubscriptionController extends BaseController
 
         helper('form');
 
+        $this->setHtmlHead(lang('Subscription.podcast_subscriptions'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -109,6 +110,7 @@ class SubscriptionController extends BaseController
             'subscription' => $this->subscription,
         ];
 
+        $this->setHtmlHead(lang('Subscription.view', [$this->subscription->id]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => '#' . $this->subscription->id,
@@ -124,6 +126,7 @@ class SubscriptionController extends BaseController
             'podcast' => $this->podcast,
         ];
 
+        $this->setHtmlHead(lang('Subscription.add', [esc($this->podcast->title)]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
         ]);
@@ -250,6 +253,7 @@ class SubscriptionController extends BaseController
             'subscription' => $this->subscription,
         ];
 
+        $this->setHtmlHead(lang('Subscription.edit', [esc($this->podcast->title)]));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => '#' . $this->subscription->id,
@@ -318,6 +322,7 @@ class SubscriptionController extends BaseController
             'subscription' => $this->subscription,
         ];
 
+        $this->setHtmlHead(lang('Subscription.suspend'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => '#' . $this->subscription->id,
@@ -413,6 +418,7 @@ class SubscriptionController extends BaseController
             'subscription' => $this->subscription,
         ];
 
+        $this->setHtmlHead(lang('Subscription.delete'));
         replace_breadcrumb_params([
             0 => $this->podcast->at_handle,
             1 => '#' . $this->subscription->id,
diff --git a/modules/PremiumPodcasts/Entities/Subscription.php b/modules/PremiumPodcasts/Entities/Subscription.php
index edca9bd4cb959c89f970a2fb47b4cc5f88bf900b..99ead6904d5ca75f464b3cb16bf04fad48ef30e2 100644
--- a/modules/PremiumPodcasts/Entities/Subscription.php
+++ b/modules/PremiumPodcasts/Entities/Subscription.php
@@ -58,9 +58,7 @@ class Subscription extends Entity
 
     public function getStatus(): string
     {
-        return ($this->expires_at instanceof Time && $this->expires_at->isBefore(
-            Time::now()
-        )) ? 'expired' : $this->attributes['status'];
+        return $this->expires_at->isBefore(Time::now()) ? 'expired' : $this->attributes['status'];
     }
 
     /**
diff --git a/package.json b/package.json
index 5701d79cdc456808335732f20ddf41db8f63d5d4..36cc5a50ba8dc7df77a1555a6a00da4bf6a7a661 100644
--- a/package.json
+++ b/package.json
@@ -31,70 +31,71 @@
     "@amcharts/amcharts4": "^4.10.39",
     "@amcharts/amcharts4-geodata": "^4.1.30",
     "@codemirror/commands": "^6.7.1",
+    "@codemirror/lang-html": "^6.4.9",
     "@codemirror/lang-xml": "^6.1.0",
-    "@codemirror/language": "^6.10.3",
-    "@codemirror/state": "^6.4.1",
-    "@codemirror/view": "^6.34.2",
+    "@codemirror/language": "^6.10.7",
+    "@codemirror/state": "^6.5.0",
+    "@codemirror/view": "^6.35.3",
     "@floating-ui/dom": "^1.6.12",
     "@github/clipboard-copy-element": "^1.3.0",
     "@github/hotkey": "^3.1.1",
     "@github/markdown-toolbar-element": "^2.2.3",
-    "@github/relative-time-element": "^4.4.3",
+    "@github/relative-time-element": "^4.4.4",
     "@patternfly/elements": "^4.0.2",
-    "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
     "@vime/core": "^5.4.1",
     "choices.js": "^11.0.2",
     "codemirror": "^6.0.1",
     "flatpickr": "^4.6.13",
+    "htmlfy": "^0.5.0",
     "leaflet": "^1.9.4",
     "leaflet.markercluster": "^1.5.3",
     "lit": "^3.2.1",
-    "marked": "^14.1.3",
-    "wavesurfer.js": "^7.8.8",
+    "marked": "^15.0.4",
+    "wavesurfer.js": "^7.8.11",
     "xml-formatter": "^3.6.3"
   },
   "devDependencies": {
-    "@commitlint/cli": "^19.5.0",
-    "@commitlint/config-conventional": "^19.5.0",
+    "@commitlint/cli": "^19.6.1",
+    "@commitlint/config-conventional": "^19.6.0",
     "@csstools/css-tokenizer": "^3.0.3",
-    "@eslint/eslintrc": "^3.1.0",
-    "@eslint/js": "^9.14.0",
+    "@eslint/eslintrc": "^3.2.0",
+    "@eslint/js": "^9.17.0",
     "@semantic-release/changelog": "^6.0.3",
     "@semantic-release/exec": "^6.0.3",
     "@semantic-release/git": "^10.0.1",
-    "@semantic-release/gitlab": "^13.2.1",
+    "@semantic-release/gitlab": "^13.2.3",
     "@tailwindcss/forms": "^0.5.9",
     "@tailwindcss/typography": "^0.5.15",
     "@types/eslint__js": "^8.42.3",
-    "@types/leaflet": "^1.9.14",
+    "@types/leaflet": "^1.9.15",
     "all-contributors-cli": "^6.26.1",
     "commitizen": "^4.3.1",
     "cross-env": "^7.0.3",
     "cssnano": "^7.0.6",
     "cz-conventional-changelog": "^3.3.0",
-    "eslint": "^9.14.0",
+    "eslint": "^9.17.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.2.1",
-    "globals": "^15.12.0",
-    "husky": "^9.1.6",
-    "is-ci": "^3.0.1",
-    "lint-staged": "^15.2.10",
-    "postcss": "^8.4.47",
+    "globals": "^15.13.0",
+    "husky": "^9.1.7",
+    "is-ci": "^4.1.0",
+    "lint-staged": "^15.2.11",
+    "postcss": "^8.4.49",
     "postcss-import": "^16.1.0",
     "postcss-nesting": "^13.0.1",
-    "postcss-preset-env": "^10.0.9",
+    "postcss-preset-env": "^10.1.2",
     "postcss-reporter": "^7.1.0",
-    "prettier": "3.3.3",
+    "prettier": "3.4.2",
     "prettier-plugin-organize-imports": "^4.1.0",
     "semantic-release": "^24.2.0",
-    "stylelint": "^16.10.0",
+    "stylelint": "^16.12.0",
     "stylelint-config-standard": "^36.0.1",
     "svgo": "^3.3.2",
-    "tailwindcss": "^3.4.14",
-    "typescript": "~5.5.4",
-    "typescript-eslint": "^8.13.0",
-    "vite": "^5.4.10",
-    "vite-plugin-pwa": "^0.20.5",
+    "tailwindcss": "^3.4.16",
+    "typescript": "~5.7.2",
+    "typescript-eslint": "^8.18.1",
+    "vite": "^6.0.3",
+    "vite-plugin-pwa": "^0.21.1",
     "workbox-build": "^7.3.0",
     "workbox-core": "^7.3.0",
     "workbox-routing": "^7.3.0",
diff --git a/phpstan.neon b/phpstan.neon
index 20bda6f1b04ac1eda5e395b1741e9787a4c767fc..792fb1dc5e19e84ade2a54f9a5c6f84c2abb2178 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -49,4 +49,5 @@ parameters:
             - Modules\PremiumPodcasts\Config\Services
     ignoreErrors:
         - '#^Call to an undefined method CodeIgniter\\Cache\\CacheInterface\:\:deleteMatching\(\)#'
-        - identifier: missingType.generics
\ No newline at end of file
+        - identifier: missingType.generics
+        - identifier: property.readOnlyByPhpDocDefaultValue
\ No newline at end of file
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index aa9d002b9332a3b0ee52574aabc7bf8c8e943d62..766a18f56aedb65f941f510572434bd449604f37 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,24 +1,30 @@
+<?xml version="1.0"?>
 <phpunit
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
-    bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php"
-    backupGlobals="false"
-    beStrictAboutOutputDuringTests="true"
-    colors="true"
-    columns="max"
-    failOnRisky="true"
-    failOnWarning="true"
-    cacheDirectory="build/.phpunit.cache">
-    <coverage
-        includeUncoveredFiles="true"
-        pathCoverage="false"
-        ignoreDeprecatedCodeUnits="true"
-        disableCodeCoverageIgnore="true">
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
+  bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php"
+  backupGlobals="false"
+  beStrictAboutOutputDuringTests="true"
+  colors="true"
+  columns="max"
+  failOnRisky="true"
+  failOnWarning="true"
+  displayDetailsOnTestsThatTriggerDeprecations="true"
+  displayDetailsOnTestsThatTriggerErrors="true"
+  displayDetailsOnTestsThatTriggerNotices="true"
+  displayDetailsOnTestsThatTriggerWarnings="true"
+  displayDetailsOnPhpunitDeprecations="true"
+  cacheDirectory="build/.phpunit.cache"
+>
+  <coverage
+    pathCoverage="false"
+    ignoreDeprecatedCodeUnits="true"
+    disableCodeCoverageIgnore="true">
     <report>
-      <clover outputFile="build/logs/clover.xml"/>
-      <html outputDirectory="build/logs/html"/>
-      <php outputFile="build/logs/coverage.serialized"/>
-      <text outputFile="php://stdout" showUncoveredFiles="false"/>
+      <clover outputFile="build/logs/clover.xml" />
+      <html outputDirectory="build/logs/html" />
+      <php outputFile="build/logs/coverage.serialized" />
+      <text outputFile="php://stdout" showUncoveredFiles="false" />
     </report>
   </coverage>
   <testsuites>
@@ -27,34 +33,34 @@
     </testsuite>
   </testsuites>
   <logging>
-    <testdoxHtml outputFile="build/logs/testdox.html"/>
-    <testdoxText outputFile="build/logs/testdox.txt"/>
-    <junit outputFile="build/logs/logfile.xml"/>
+    <testdoxHtml outputFile="build/logs/testdox.html" />
+    <testdoxText outputFile="build/logs/testdox.txt" />
+    <junit outputFile="build/logs/logfile.xml" />
   </logging>
   <source>
     <include>
-        <directory suffix=".php">./app</directory>
+      <directory suffix=".php">./app</directory>
     </include>
     <exclude>
-        <directory suffix=".php">./app/Views</directory>
-        <file>./app/Config/Routes.php</file>
+      <directory suffix=".php">./app/Views</directory>
+      <file>./app/Config/Routes.php</file>
     </exclude>
   </source>
   <php>
-    <server name="app.baseURL" value="http://example.com/"/>
+    <server name="app.baseURL" value="http://example.com/" />
     <!-- Directory containing phpunit.xml -->
-    <const name="HOMEPATH" value="./"/>
+    <const name="HOMEPATH" value="./" />
     <!-- Directory containing the Paths config file -->
-    <const name="CONFIGPATH" value="./app/Config/"/>
+    <const name="CONFIGPATH" value="./app/Config/" />
     <!-- Directory containing the front controller (index.php) -->
-    <const name="PUBLICPATH" value="./public/"/>
+    <const name="PUBLICPATH" value="./public/" />
     <!-- Database configuration -->
-    <env name="database.tests.hostname" value="mariadb"/>
-    <env name="database.tests.database" value="test"/>
-    <env name="database.tests.username" value="castopod"/>
-    <env name="database.tests.password" value="castopod"/>
-    <env name="database.tests.DBDriver" value="MySQLi"/>
-    <env name="database.tests.DBPrefix" value="tests_"/>
-    <env name="restapi.enabled" value="true"/>
+    <env name="database.tests.hostname" value="mariadb" />
+    <env name="database.tests.database" value="test" />
+    <env name="database.tests.username" value="castopod" />
+    <env name="database.tests.password" value="castopod" />
+    <env name="database.tests.DBDriver" value="MySQLi" />
+    <env name="database.tests.DBPrefix" value="tests_" />
+    <env name="restapi.enabled" value="true" />
   </php>
-</phpunit>
+</phpunit>
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e581c610243e7323c7d8e7b946ce831fad326075..f08d739e7758db8b59eb1530c91fa0ada7438a25 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,18 +16,21 @@ importers:
       "@codemirror/commands":
         specifier: ^6.7.1
         version: 6.7.1
+      "@codemirror/lang-html":
+        specifier: ^6.4.9
+        version: 6.4.9
       "@codemirror/lang-xml":
         specifier: ^6.1.0
         version: 6.1.0
       "@codemirror/language":
-        specifier: ^6.10.3
-        version: 6.10.3
+        specifier: ^6.10.7
+        version: 6.10.7
       "@codemirror/state":
-        specifier: ^6.4.1
-        version: 6.4.1
+        specifier: ^6.5.0
+        version: 6.5.0
       "@codemirror/view":
-        specifier: ^6.34.2
-        version: 6.34.2
+        specifier: ^6.35.3
+        version: 6.35.3
       "@floating-ui/dom":
         specifier: ^1.6.12
         version: 1.6.12
@@ -41,14 +44,11 @@ importers:
         specifier: ^2.2.3
         version: 2.2.3
       "@github/relative-time-element":
-        specifier: ^4.4.3
-        version: 4.4.3
+        specifier: ^4.4.4
+        version: 4.4.4
       "@patternfly/elements":
         specifier: ^4.0.2
         version: 4.0.2
-      "@tailwindcss/nesting":
-        specifier: 0.0.0-insiders.565cd3e
-        version: 0.0.0-insiders.565cd3e(postcss@8.4.47)
       "@vime/core":
         specifier: ^5.4.1
         version: 5.4.1
@@ -61,6 +61,9 @@ importers:
       flatpickr:
         specifier: ^4.6.13
         version: 4.6.13
+      htmlfy:
+        specifier: ^0.5.0
+        version: 0.5.0
       leaflet:
         specifier: ^1.9.4
         version: 1.9.4
@@ -71,138 +74,138 @@ importers:
         specifier: ^3.2.1
         version: 3.2.1
       marked:
-        specifier: ^14.1.3
-        version: 14.1.3
+        specifier: ^15.0.4
+        version: 15.0.4
       wavesurfer.js:
-        specifier: ^7.8.8
-        version: 7.8.8
+        specifier: ^7.8.11
+        version: 7.8.11
       xml-formatter:
         specifier: ^3.6.3
         version: 3.6.3
     devDependencies:
       "@commitlint/cli":
-        specifier: ^19.5.0
-        version: 19.5.0(@types/node@22.9.0)(typescript@5.5.4)
+        specifier: ^19.6.1
+        version: 19.6.1(@types/node@22.9.0)(typescript@5.7.2)
       "@commitlint/config-conventional":
-        specifier: ^19.5.0
-        version: 19.5.0
+        specifier: ^19.6.0
+        version: 19.6.0
       "@csstools/css-tokenizer":
         specifier: ^3.0.3
         version: 3.0.3
       "@eslint/eslintrc":
-        specifier: ^3.1.0
-        version: 3.1.0
+        specifier: ^3.2.0
+        version: 3.2.0
       "@eslint/js":
-        specifier: ^9.14.0
-        version: 9.14.0
+        specifier: ^9.17.0
+        version: 9.17.0
       "@semantic-release/changelog":
         specifier: ^6.0.3
-        version: 6.0.3(semantic-release@24.2.0(typescript@5.5.4))
+        version: 6.0.3(semantic-release@24.2.0(typescript@5.7.2))
       "@semantic-release/exec":
         specifier: ^6.0.3
-        version: 6.0.3(semantic-release@24.2.0(typescript@5.5.4))
+        version: 6.0.3(semantic-release@24.2.0(typescript@5.7.2))
       "@semantic-release/git":
         specifier: ^10.0.1
-        version: 10.0.1(semantic-release@24.2.0(typescript@5.5.4))
+        version: 10.0.1(semantic-release@24.2.0(typescript@5.7.2))
       "@semantic-release/gitlab":
-        specifier: ^13.2.1
-        version: 13.2.1(semantic-release@24.2.0(typescript@5.5.4))
+        specifier: ^13.2.3
+        version: 13.2.3(semantic-release@24.2.0(typescript@5.7.2))
       "@tailwindcss/forms":
         specifier: ^0.5.9
-        version: 0.5.9(tailwindcss@3.4.14)
+        version: 0.5.9(tailwindcss@3.4.16)
       "@tailwindcss/typography":
         specifier: ^0.5.15
-        version: 0.5.15(tailwindcss@3.4.14)
+        version: 0.5.15(tailwindcss@3.4.16)
       "@types/eslint__js":
         specifier: ^8.42.3
         version: 8.42.3
       "@types/leaflet":
-        specifier: ^1.9.14
-        version: 1.9.14
+        specifier: ^1.9.15
+        version: 1.9.15
       all-contributors-cli:
         specifier: ^6.26.1
         version: 6.26.1
       commitizen:
         specifier: ^4.3.1
-        version: 4.3.1(@types/node@22.9.0)(typescript@5.5.4)
+        version: 4.3.1(@types/node@22.9.0)(typescript@5.7.2)
       cross-env:
         specifier: ^7.0.3
         version: 7.0.3
       cssnano:
         specifier: ^7.0.6
-        version: 7.0.6(postcss@8.4.47)
+        version: 7.0.6(postcss@8.4.49)
       cz-conventional-changelog:
         specifier: ^3.3.0
-        version: 3.3.0(@types/node@22.9.0)(typescript@5.5.4)
+        version: 3.3.0(@types/node@22.9.0)(typescript@5.7.2)
       eslint:
-        specifier: ^9.14.0
-        version: 9.14.0(jiti@1.21.6)
+        specifier: ^9.17.0
+        version: 9.17.0(jiti@2.4.1)
       eslint-config-prettier:
         specifier: ^9.1.0
-        version: 9.1.0(eslint@9.14.0(jiti@1.21.6))
+        version: 9.1.0(eslint@9.17.0(jiti@2.4.1))
       eslint-plugin-prettier:
         specifier: ^5.2.1
-        version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6))(prettier@3.3.3)
+        version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1))(prettier@3.4.2)
       globals:
-        specifier: ^15.12.0
-        version: 15.12.0
+        specifier: ^15.13.0
+        version: 15.13.0
       husky:
-        specifier: ^9.1.6
-        version: 9.1.6
+        specifier: ^9.1.7
+        version: 9.1.7
       is-ci:
-        specifier: ^3.0.1
-        version: 3.0.1
+        specifier: ^4.1.0
+        version: 4.1.0
       lint-staged:
-        specifier: ^15.2.10
-        version: 15.2.10
+        specifier: ^15.2.11
+        version: 15.2.11
       postcss:
-        specifier: ^8.4.47
-        version: 8.4.47
+        specifier: ^8.4.49
+        version: 8.4.49
       postcss-import:
         specifier: ^16.1.0
-        version: 16.1.0(postcss@8.4.47)
+        version: 16.1.0(postcss@8.4.49)
       postcss-nesting:
         specifier: ^13.0.1
-        version: 13.0.1(postcss@8.4.47)
+        version: 13.0.1(postcss@8.4.49)
       postcss-preset-env:
-        specifier: ^10.0.9
-        version: 10.0.9(postcss@8.4.47)
+        specifier: ^10.1.2
+        version: 10.1.2(postcss@8.4.49)
       postcss-reporter:
         specifier: ^7.1.0
-        version: 7.1.0(postcss@8.4.47)
+        version: 7.1.0(postcss@8.4.49)
       prettier:
-        specifier: 3.3.3
-        version: 3.3.3
+        specifier: 3.4.2
+        version: 3.4.2
       prettier-plugin-organize-imports:
         specifier: ^4.1.0
-        version: 4.1.0(prettier@3.3.3)(typescript@5.5.4)
+        version: 4.1.0(prettier@3.4.2)(typescript@5.7.2)
       semantic-release:
         specifier: ^24.2.0
-        version: 24.2.0(typescript@5.5.4)
+        version: 24.2.0(typescript@5.7.2)
       stylelint:
-        specifier: ^16.10.0
-        version: 16.10.0(typescript@5.5.4)
+        specifier: ^16.12.0
+        version: 16.12.0(typescript@5.7.2)
       stylelint-config-standard:
         specifier: ^36.0.1
-        version: 36.0.1(stylelint@16.10.0(typescript@5.5.4))
+        version: 36.0.1(stylelint@16.12.0(typescript@5.7.2))
       svgo:
         specifier: ^3.3.2
         version: 3.3.2
       tailwindcss:
-        specifier: ^3.4.14
-        version: 3.4.14
+        specifier: ^3.4.16
+        version: 3.4.16
       typescript:
-        specifier: ~5.5.4
-        version: 5.5.4
+        specifier: ~5.7.2
+        version: 5.7.2
       typescript-eslint:
-        specifier: ^8.13.0
-        version: 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
+        specifier: ^8.18.1
+        version: 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
       vite:
-        specifier: ^5.4.10
-        version: 5.4.10(@types/node@22.9.0)(terser@5.36.0)
+        specifier: ^6.0.3
+        version: 6.0.3(@types/node@22.9.0)(jiti@2.4.1)(terser@5.36.0)(yaml@2.6.1)
       vite-plugin-pwa:
-        specifier: ^0.20.5
-        version: 0.20.5(vite@5.4.10(@types/node@22.9.0)(terser@5.36.0))(workbox-build@7.3.0)(workbox-window@7.3.0)
+        specifier: ^0.21.1
+        version: 0.21.1(vite@6.0.3(@types/node@22.9.0)(jiti@2.4.1)(terser@5.36.0)(yaml@2.6.1))(workbox-build@7.3.0)(workbox-window@7.3.0)
       workbox-build:
         specifier: ^7.3.0
         version: 7.3.0
@@ -1032,16 +1035,34 @@ packages:
         integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==,
       }
 
+  "@codemirror/lang-css@6.3.1":
+    resolution:
+      {
+        integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==,
+      }
+
+  "@codemirror/lang-html@6.4.9":
+    resolution:
+      {
+        integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==,
+      }
+
+  "@codemirror/lang-javascript@6.2.2":
+    resolution:
+      {
+        integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==,
+      }
+
   "@codemirror/lang-xml@6.1.0":
     resolution:
       {
         integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==,
       }
 
-  "@codemirror/language@6.10.3":
+  "@codemirror/language@6.10.7":
     resolution:
       {
-        integrity: sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==,
+        integrity: sha512-aOswhVOLYhMNeqykt4P7+ukQSpGL0ynZYaEyFDVHE7fl2xgluU3yuE9MdgYNfw6EmaNidoFMIQ2iTh1ADrnT6A==,
       }
 
   "@codemirror/lint@6.8.2":
@@ -1056,16 +1077,16 @@ packages:
         integrity: sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==,
       }
 
-  "@codemirror/state@6.4.1":
+  "@codemirror/state@6.5.0":
     resolution:
       {
-        integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==,
+        integrity: sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==,
       }
 
-  "@codemirror/view@6.34.2":
+  "@codemirror/view@6.35.3":
     resolution:
       {
-        integrity: sha512-d6n0WFvL970A9Z+l9N2dO+Hk9ev4hDYQzIx+B9tCyBP0W5wPEszi1rhuyFesNSkLZzXbQE5FPH7F/z/TMJfoPA==,
+        integrity: sha512-ScY7L8+EGdPl4QtoBiOzE4FELp7JmNUsBvgBcCakXWM2uiv/K89VAzU3BMDscf0DsACLvTKePbd5+cFDTcei6g==,
       }
 
   "@colors/colors@1.5.0":
@@ -1075,18 +1096,18 @@ packages:
       }
     engines: { node: ">=0.1.90" }
 
-  "@commitlint/cli@19.5.0":
+  "@commitlint/cli@19.6.1":
     resolution:
       {
-        integrity: sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==,
+        integrity: sha512-8hcyA6ZoHwWXC76BoC8qVOSr8xHy00LZhZpauiD0iO0VYbVhMnED0da85lTfIULxl7Lj4c6vZgF0Wu/ed1+jlQ==,
       }
     engines: { node: ">=v18" }
     hasBin: true
 
-  "@commitlint/config-conventional@19.5.0":
+  "@commitlint/config-conventional@19.6.0":
     resolution:
       {
-        integrity: sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==,
+        integrity: sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ==,
       }
     engines: { node: ">=v18" }
 
@@ -1118,17 +1139,17 @@ packages:
       }
     engines: { node: ">=v18" }
 
-  "@commitlint/is-ignored@19.5.0":
+  "@commitlint/is-ignored@19.6.0":
     resolution:
       {
-        integrity: sha512-0XQ7Llsf9iL/ANtwyZ6G0NGp5Y3EQ8eDQSxv/SRcfJ0awlBY4tHFAvwWbw66FVUaWICH7iE5en+FD9TQsokZ5w==,
+        integrity: sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw==,
       }
     engines: { node: ">=v18" }
 
-  "@commitlint/lint@19.5.0":
+  "@commitlint/lint@19.6.0":
     resolution:
       {
-        integrity: sha512-cAAQwJcRtiBxQWO0eprrAbOurtJz8U6MgYqLz+p9kLElirzSCc0vGMcyCaA1O7AqBuxo11l1XsY3FhOFowLAAg==,
+        integrity: sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg==,
       }
     engines: { node: ">=v18" }
 
@@ -1139,6 +1160,13 @@ packages:
       }
     engines: { node: ">=v18" }
 
+  "@commitlint/load@19.6.1":
+    resolution:
+      {
+        integrity: sha512-kE4mRKWWNju2QpsCWt428XBvUH55OET2N4QKQ0bF85qS/XbsRGG1MiTByDNlEVpEPceMkDr46LNH95DtRwcsfA==,
+      }
+    engines: { node: ">=v18" }
+
   "@commitlint/message@19.5.0":
     resolution:
       {
@@ -1167,10 +1195,10 @@ packages:
       }
     engines: { node: ">=v18" }
 
-  "@commitlint/rules@19.5.0":
+  "@commitlint/rules@19.6.0":
     resolution:
       {
-        integrity: sha512-hDW5TPyf/h1/EufSHEKSp6Hs+YVsDMHazfJ2azIk9tHPXS6UqSz1dIRs1gpqS3eMXgtkT7JH6TW4IShdqOwhAw==,
+        integrity: sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw==,
       }
     engines: { node: ">=v18" }
 
@@ -1212,20 +1240,20 @@ packages:
       }
     engines: { node: ">=18" }
 
-  "@csstools/css-calc@2.0.4":
+  "@csstools/css-calc@2.1.0":
     resolution:
       {
-        integrity: sha512-8/iCd8lH10gKNsq5detnbGWiFd6PXK2wB8wjE6fHNNhtqvshyMrIJgffwRcw6yl/gzGTH+N1i+KRhjqHxqYTmg==,
+        integrity: sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw==,
       }
     engines: { node: ">=18" }
     peerDependencies:
       "@csstools/css-parser-algorithms": ^3.0.4
       "@csstools/css-tokenizer": ^3.0.3
 
-  "@csstools/css-color-parser@3.0.5":
+  "@csstools/css-color-parser@3.0.6":
     resolution:
       {
-        integrity: sha512-4Wo8raj9YF3PnZ5iGrAl+BSsk2MYBOEUS/X4k1HL9mInhyCVftEG02MywdvelXlwZGUF2XTQ0qj9Jd398mhqrw==,
+        integrity: sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1248,16 +1276,6 @@ packages:
       }
     engines: { node: ">=18" }
 
-  "@csstools/media-query-list-parser@3.0.1":
-    resolution:
-      {
-        integrity: sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==,
-      }
-    engines: { node: ">=18" }
-    peerDependencies:
-      "@csstools/css-parser-algorithms": ^3.0.1
-      "@csstools/css-tokenizer": ^3.0.1
-
   "@csstools/media-query-list-parser@4.0.2":
     resolution:
       {
@@ -1277,19 +1295,19 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-color-function@4.0.5":
+  "@csstools/postcss-color-function@4.0.6":
     resolution:
       {
-        integrity: sha512-6dHr2NDsBMiZCPkGDi2qMfIbzV2kWV8Dh7SVb1FZGnN/r2TI4TSAkVF8rCG5L70yQZHMcQGB84yp8Zm+RGhoHA==,
+        integrity: sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ==,
       }
     engines: { node: ">=18" }
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-color-mix-function@3.0.5":
+  "@csstools/postcss-color-mix-function@3.0.6":
     resolution:
       {
-        integrity: sha512-jgq0oGbit7TxWYP8y2hWWfV64xzcAgJk54PBYZ2fDrRgEDy1l5YMCrFawnn+5JETh/E1jjXPDFhFEYhwr3vA3g==,
+        integrity: sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1304,10 +1322,10 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-exponential-functions@2.0.4":
+  "@csstools/postcss-exponential-functions@2.0.5":
     resolution:
       {
-        integrity: sha512-xmzFCGTkkLDs7q9vVaRGlnu8s51lRRJzHsaJ/nXmkQuyg0q7gh7rTbJ0bY5sSVet+KB7MTIxRXRUCl2tm7RODA==,
+        integrity: sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1322,28 +1340,28 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-gamut-mapping@2.0.5":
+  "@csstools/postcss-gamut-mapping@2.0.6":
     resolution:
       {
-        integrity: sha512-VQDayRhC/Mg1fuo8/4F43La5aROgvVyqtCqdNyGvRKi6L1+zXfwQ583nImi7k/gn2GNJH82Bf9mutTuT1GtXzA==,
+        integrity: sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA==,
       }
     engines: { node: ">=18" }
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-gradients-interpolation-method@5.0.5":
+  "@csstools/postcss-gradients-interpolation-method@5.0.6":
     resolution:
       {
-        integrity: sha512-l3ShDdAt/szbyBh3Jz27MRFt5WPAbnVCMsU7Vs7EbBxJQNgVDrcu1APBB2nPagDJOyhI6/IahuW7nb6grWVTpA==,
+        integrity: sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg==,
       }
     engines: { node: ">=18" }
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-hwb-function@4.0.5":
+  "@csstools/postcss-hwb-function@4.0.6":
     resolution:
       {
-        integrity: sha512-bPn/SQyiiYjWkwK2ykc7O9LliMR50YfUGukd6jQI2okHzB7NxNt/IS45tS1Muk7Hhf3B9Lbmg1Ofq36tBmM92Q==,
+        integrity: sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1430,10 +1448,10 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-media-minmax@2.0.4":
+  "@csstools/postcss-media-minmax@2.0.5":
     resolution:
       {
-        integrity: sha512-zgdBOCI9aKoy5GK9tb/3ve0pl7vH0HJg7rfQEWT3TZiIKh7XEWucDSTSwnwgdgtgz50UxrOfbK+C59M+u2fE2Q==,
+        integrity: sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1466,10 +1484,10 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-oklab-function@4.0.5":
+  "@csstools/postcss-oklab-function@4.0.6":
     resolution:
       {
-        integrity: sha512-19bsJQFyJNSEhpaVq0Mq1E0HDXfx8qMHa/bR1MaHr1UD4DWvM2/J6YXb9OVGS7eFl92Y3c84Yggn9uFv13vsiQ==,
+        integrity: sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1484,10 +1502,19 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-relative-color-syntax@3.0.5":
+  "@csstools/postcss-random-function@1.0.1":
     resolution:
       {
-        integrity: sha512-5VrE4hAwv/ZpuL1Yo0ZGGFi1QPpIikp/rzz7LnpQ31ACQVRIA5/M9qZmJbRlZVsJ4bUFSQ3dq6kHSHrCt2uM6Q==,
+        integrity: sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA==,
+      }
+    engines: { node: ">=18" }
+    peerDependencies:
+      postcss: ^8.4
+
+  "@csstools/postcss-relative-color-syntax@3.0.6":
+    resolution:
+      {
+        integrity: sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1502,10 +1529,19 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-stepped-value-functions@4.0.4":
+  "@csstools/postcss-sign-functions@1.1.0":
     resolution:
       {
-        integrity: sha512-JjShuWZkmIOT8EfI7lYjl7V5qM29LNDdnnSo5O7v/InJJHfeiQjtxyAaZzKGXzpkghPrCAcgLfJ+IyqTdXo7IA==,
+        integrity: sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA==,
+      }
+    engines: { node: ">=18" }
+    peerDependencies:
+      postcss: ^8.4
+
+  "@csstools/postcss-stepped-value-functions@4.0.5":
+    resolution:
+      {
+        integrity: sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1520,10 +1556,10 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  "@csstools/postcss-trigonometric-functions@4.0.4":
+  "@csstools/postcss-trigonometric-functions@4.0.5":
     resolution:
       {
-        integrity: sha512-nn+gWTZZlSnwbyUtGQCnvBXIx1TX+HVStvIm3221dWGQvp47bB5giMBbuAK4a/UJGBbfDQhGKEbYq++WWM1i1A==,
+        integrity: sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -1547,15 +1583,6 @@ packages:
     peerDependencies:
       postcss-selector-parser: ^7.0.0
 
-  "@csstools/selector-specificity@4.0.0":
-    resolution:
-      {
-        integrity: sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==,
-      }
-    engines: { node: ">=18" }
-    peerDependencies:
-      postcss-selector-parser: ^6.1.0
-
   "@csstools/selector-specificity@5.0.0":
     resolution:
       {
@@ -1580,210 +1607,219 @@ packages:
         integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==,
       }
 
-  "@esbuild/aix-ppc64@0.21.5":
+  "@esbuild/aix-ppc64@0.24.0":
     resolution:
       {
-        integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==,
+        integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [ppc64]
     os: [aix]
 
-  "@esbuild/android-arm64@0.21.5":
+  "@esbuild/android-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==,
+        integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm64]
     os: [android]
 
-  "@esbuild/android-arm@0.21.5":
+  "@esbuild/android-arm@0.24.0":
     resolution:
       {
-        integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==,
+        integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm]
     os: [android]
 
-  "@esbuild/android-x64@0.21.5":
+  "@esbuild/android-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==,
+        integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [android]
 
-  "@esbuild/darwin-arm64@0.21.5":
+  "@esbuild/darwin-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==,
+        integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm64]
     os: [darwin]
 
-  "@esbuild/darwin-x64@0.21.5":
+  "@esbuild/darwin-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==,
+        integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [darwin]
 
-  "@esbuild/freebsd-arm64@0.21.5":
+  "@esbuild/freebsd-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==,
+        integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm64]
     os: [freebsd]
 
-  "@esbuild/freebsd-x64@0.21.5":
+  "@esbuild/freebsd-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==,
+        integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [freebsd]
 
-  "@esbuild/linux-arm64@0.21.5":
+  "@esbuild/linux-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==,
+        integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm64]
     os: [linux]
 
-  "@esbuild/linux-arm@0.21.5":
+  "@esbuild/linux-arm@0.24.0":
     resolution:
       {
-        integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==,
+        integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm]
     os: [linux]
 
-  "@esbuild/linux-ia32@0.21.5":
+  "@esbuild/linux-ia32@0.24.0":
     resolution:
       {
-        integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==,
+        integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [ia32]
     os: [linux]
 
-  "@esbuild/linux-loong64@0.21.5":
+  "@esbuild/linux-loong64@0.24.0":
     resolution:
       {
-        integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==,
+        integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [loong64]
     os: [linux]
 
-  "@esbuild/linux-mips64el@0.21.5":
+  "@esbuild/linux-mips64el@0.24.0":
     resolution:
       {
-        integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==,
+        integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [mips64el]
     os: [linux]
 
-  "@esbuild/linux-ppc64@0.21.5":
+  "@esbuild/linux-ppc64@0.24.0":
     resolution:
       {
-        integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==,
+        integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [ppc64]
     os: [linux]
 
-  "@esbuild/linux-riscv64@0.21.5":
+  "@esbuild/linux-riscv64@0.24.0":
     resolution:
       {
-        integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==,
+        integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [riscv64]
     os: [linux]
 
-  "@esbuild/linux-s390x@0.21.5":
+  "@esbuild/linux-s390x@0.24.0":
     resolution:
       {
-        integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==,
+        integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [s390x]
     os: [linux]
 
-  "@esbuild/linux-x64@0.21.5":
+  "@esbuild/linux-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==,
+        integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [linux]
 
-  "@esbuild/netbsd-x64@0.21.5":
+  "@esbuild/netbsd-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==,
+        integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [netbsd]
 
-  "@esbuild/openbsd-x64@0.21.5":
+  "@esbuild/openbsd-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==,
+        integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
+    cpu: [arm64]
+    os: [openbsd]
+
+  "@esbuild/openbsd-x64@0.24.0":
+    resolution:
+      {
+        integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==,
+      }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [openbsd]
 
-  "@esbuild/sunos-x64@0.21.5":
+  "@esbuild/sunos-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==,
+        integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [sunos]
 
-  "@esbuild/win32-arm64@0.21.5":
+  "@esbuild/win32-arm64@0.24.0":
     resolution:
       {
-        integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==,
+        integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [arm64]
     os: [win32]
 
-  "@esbuild/win32-ia32@0.21.5":
+  "@esbuild/win32-ia32@0.24.0":
     resolution:
       {
-        integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==,
+        integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [ia32]
     os: [win32]
 
-  "@esbuild/win32-x64@0.21.5":
+  "@esbuild/win32-x64@0.24.0":
     resolution:
       {
-        integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==,
+        integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     cpu: [x64]
     os: [win32]
 
@@ -1803,45 +1839,45 @@ packages:
       }
     engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 }
 
-  "@eslint/config-array@0.18.0":
+  "@eslint/config-array@0.19.1":
     resolution:
       {
-        integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==,
+        integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@eslint/core@0.7.0":
+  "@eslint/core@0.9.1":
     resolution:
       {
-        integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==,
+        integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@eslint/eslintrc@3.1.0":
+  "@eslint/eslintrc@3.2.0":
     resolution:
       {
-        integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==,
+        integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@eslint/js@9.14.0":
+  "@eslint/js@9.17.0":
     resolution:
       {
-        integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==,
+        integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@eslint/object-schema@2.1.4":
+  "@eslint/object-schema@2.1.5":
     resolution:
       {
-        integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==,
+        integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@eslint/plugin-kit@0.2.2":
+  "@eslint/plugin-kit@0.2.4":
     resolution:
       {
-        integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==,
+        integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
@@ -1905,10 +1941,10 @@ packages:
         integrity: sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==,
       }
 
-  "@github/relative-time-element@4.4.3":
+  "@github/relative-time-element@4.4.4":
     resolution:
       {
-        integrity: sha512-EVKokqx9/DdUAZ2l9WVyY51EtRCO2gQWWMvsRIn7r4glJ91q9CXcnILVHZVCpfD52ucXUhUvtYsAjNJ4qP4uIg==,
+        integrity: sha512-Oi8uOL8O+ZWLD7dHRWCkm2cudcTYtB3VyOYf9BtzCgDGm+OKomyOREtItNMtWl1dxvec62BTKErq36uy+RYxQg==,
       }
 
   "@humanfs/core@0.19.1":
@@ -1998,12 +2034,30 @@ packages:
         integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==,
       }
 
+  "@lezer/css@1.1.9":
+    resolution:
+      {
+        integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==,
+      }
+
   "@lezer/highlight@1.2.1":
     resolution:
       {
         integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==,
       }
 
+  "@lezer/html@1.3.10":
+    resolution:
+      {
+        integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==,
+      }
+
+  "@lezer/javascript@1.4.21":
+    resolution:
+      {
+        integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==,
+      }
+
   "@lezer/lr@1.4.2":
     resolution:
       {
@@ -2034,6 +2088,12 @@ packages:
         integrity: sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==,
       }
 
+  "@marijn/find-cluster-break@1.0.2":
+    resolution:
+      {
+        integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==,
+      }
+
   "@nodelib/fs.scandir@2.1.5":
     resolution:
       {
@@ -2465,10 +2525,10 @@ packages:
     peerDependencies:
       semantic-release: ">=24.1.0"
 
-  "@semantic-release/gitlab@13.2.1":
+  "@semantic-release/gitlab@13.2.3":
     resolution:
       {
-        integrity: sha512-uvajKL3RbvouunvF6rWCEWmvYFW5tCq8irXvb2tW40cdI5rK22q9Uma1m/PxPtUTjggqfap86G0gst1hgeFuow==,
+        integrity: sha512-AzH/s7r8CLDN8dnbkrXnC+Gy9NYG/qRIIKMalaqNFAorgR+goGcqMb/6vIY9aVvwaoT1bo8xr1A+eeuuL4dGEQ==,
       }
     engines: { node: ">=20.8.1" }
     peerDependencies:
@@ -2549,14 +2609,6 @@ packages:
     peerDependencies:
       tailwindcss: ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20"
 
-  "@tailwindcss/nesting@0.0.0-insiders.565cd3e":
-    resolution:
-      {
-        integrity: sha512-WhHoFBx19TnH/c+xLwT/sxei6+4RpdfiyG3MYXfmLaMsADmVqBkF7B6lDalgZD9YdM459MF7DtxVbWkOrV7IaQ==,
-      }
-    peerDependencies:
-      postcss: ^8.2.15
-
   "@tailwindcss/typography@0.5.15":
     resolution:
       {
@@ -2626,10 +2678,10 @@ packages:
         integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==,
       }
 
-  "@types/leaflet@1.9.14":
+  "@types/leaflet@1.9.15":
     resolution:
       {
-        integrity: sha512-sx2q6MDJaajwhKeVgPSvqXd8rhNJSTA3tMidQGduZn9S6WBYxDkCpSpV5xXEmSg7Cgdk/5vJGhVF1kMYLzauBg==,
+        integrity: sha512-7UuggAuAs+mva66gtf2OTB1nEhzU/9JED93TIaOEgvFMvG/dIGQaukHE7izHo1Zd+Ko1L4ETUw7TBc8yUxevpg==,
       }
 
   "@types/node@22.9.0":
@@ -2668,84 +2720,74 @@ packages:
         integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==,
       }
 
-  "@typescript-eslint/eslint-plugin@8.13.0":
+  "@typescript-eslint/eslint-plugin@8.18.1":
     resolution:
       {
-        integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==,
+        integrity: sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
       "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0
       eslint: ^8.57.0 || ^9.0.0
-      typescript: "*"
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: ">=4.8.4 <5.8.0"
 
-  "@typescript-eslint/parser@8.13.0":
+  "@typescript-eslint/parser@8.18.1":
     resolution:
       {
-        integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==,
+        integrity: sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
-      typescript: "*"
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: ">=4.8.4 <5.8.0"
 
-  "@typescript-eslint/scope-manager@8.13.0":
+  "@typescript-eslint/scope-manager@8.18.1":
     resolution:
       {
-        integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==,
+        integrity: sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@typescript-eslint/type-utils@8.13.0":
+  "@typescript-eslint/type-utils@8.18.1":
     resolution:
       {
-        integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==,
+        integrity: sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
-      typescript: "*"
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: ">=4.8.4 <5.8.0"
 
-  "@typescript-eslint/types@8.13.0":
+  "@typescript-eslint/types@8.18.1":
     resolution:
       {
-        integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==,
+        integrity: sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  "@typescript-eslint/typescript-estree@8.13.0":
+  "@typescript-eslint/typescript-estree@8.18.1":
     resolution:
       {
-        integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==,
+        integrity: sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
-      typescript: "*"
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: ">=4.8.4 <5.8.0"
 
-  "@typescript-eslint/utils@8.13.0":
+  "@typescript-eslint/utils@8.18.1":
     resolution:
       {
-        integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==,
+        integrity: sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
+      typescript: ">=4.8.4 <5.8.0"
 
-  "@typescript-eslint/visitor-keys@8.13.0":
+  "@typescript-eslint/visitor-keys@8.18.1":
     resolution:
       {
-        integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==,
+        integrity: sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
@@ -3195,10 +3237,10 @@ packages:
       }
     engines: { node: ">= 8.10.0" }
 
-  ci-info@3.9.0:
+  ci-info@4.1.0:
     resolution:
       {
-        integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==,
+        integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==,
       }
     engines: { node: ">=8" }
 
@@ -3495,6 +3537,17 @@ packages:
       cosmiconfig: ">=8.2"
       typescript: ">=4"
 
+  cosmiconfig-typescript-loader@6.1.0:
+    resolution:
+      {
+        integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==,
+      }
+    engines: { node: ">=v18" }
+    peerDependencies:
+      "@types/node": "*"
+      cosmiconfig: ">=9"
+      typescript: ">=5"
+
   cosmiconfig@9.0.0:
     resolution:
       {
@@ -3528,6 +3581,13 @@ packages:
       }
     engines: { node: ">= 8" }
 
+  cross-spawn@7.0.6:
+    resolution:
+      {
+        integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==,
+      }
+    engines: { node: ">= 8" }
+
   crypto-js@4.2.0:
     resolution:
       {
@@ -3573,10 +3633,10 @@ packages:
       }
     engines: { node: ">=12 || >=16" }
 
-  css-has-pseudo@7.0.1:
+  css-has-pseudo@7.0.2:
     resolution:
       {
-        integrity: sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg==,
+        integrity: sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -3625,10 +3685,10 @@ packages:
       }
     engines: { node: ">= 6" }
 
-  cssdb@8.1.2:
+  cssdb@8.2.3:
     resolution:
       {
-        integrity: sha512-ba3HmHU/lxy9nfz/fQLA/Ul+/oSdSOXqoR53BDmRvXTfRbkGqHKqr2rSxADYMRF4uD8vZhMlCQ6c5TEfLLkkVA==,
+        integrity: sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==,
       }
 
   cssesc@3.0.0:
@@ -3807,6 +3867,18 @@ packages:
       supports-color:
         optional: true
 
+  debug@4.4.0:
+    resolution:
+      {
+        integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==,
+      }
+    engines: { node: ">=6.0" }
+    peerDependencies:
+      supports-color: "*"
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   decamelize@1.2.0:
     resolution:
       {
@@ -4078,12 +4150,12 @@ packages:
       }
     engines: { node: ">= 0.4" }
 
-  esbuild@0.21.5:
+  esbuild@0.24.0:
     resolution:
       {
-        integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==,
+        integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==,
       }
-    engines: { node: ">=12" }
+    engines: { node: ">=18" }
     hasBin: true
 
   escalade@3.2.0:
@@ -4161,10 +4233,10 @@ packages:
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
 
-  eslint@9.14.0:
+  eslint@9.17.0:
     resolution:
       {
-        integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==,
+        integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     hasBin: true
@@ -4724,10 +4796,10 @@ packages:
       }
     engines: { node: ">=18" }
 
-  globals@15.12.0:
+  globals@15.13.0:
     resolution:
       {
-        integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==,
+        integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==,
       }
     engines: { node: ">=18" }
 
@@ -4899,6 +4971,12 @@ packages:
       }
     engines: { node: ">=8" }
 
+  htmlfy@0.5.0:
+    resolution:
+      {
+        integrity: sha512-/g4imybF9k7eJT+VEsjtpx1i3BHYxFxv6/RS0Lf8veh1+pw0HzAEndGTdjvrlVRqUSu7YurJZkfnLXpVZ2yrEw==,
+      }
+
   http-cache-semantics@4.1.1:
     resolution:
       {
@@ -4947,10 +5025,10 @@ packages:
       }
     engines: { node: ">=18.18.0" }
 
-  husky@9.1.6:
+  husky@9.1.7:
     resolution:
       {
-        integrity: sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==,
+        integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==,
       }
     engines: { node: ">=18" }
     hasBin: true
@@ -5151,10 +5229,10 @@ packages:
       }
     engines: { node: ">= 0.4" }
 
-  is-ci@3.0.1:
+  is-ci@4.1.0:
     resolution:
       {
-        integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==,
+        integrity: sha512-Ab9bQDQ11lWootZUI5qxgN2ZXwxNI5hTwnsvOc1wyxQ7zQ8OkEDw79mI0+9jI3x432NfwbVRru+3noJfXF6lSQ==,
       }
     hasBin: true
 
@@ -5432,6 +5510,13 @@ packages:
       }
     hasBin: true
 
+  jiti@2.4.1:
+    resolution:
+      {
+        integrity: sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==,
+      }
+    hasBin: true
+
   jpeg-exif@1.1.4:
     resolution:
       {
@@ -5549,10 +5634,10 @@ packages:
       }
     engines: { node: ">=0.10.0" }
 
-  known-css-properties@0.34.0:
+  known-css-properties@0.35.0:
     resolution:
       {
-        integrity: sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==,
+        integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==,
       }
 
   leaflet.markercluster@1.5.3:
@@ -5583,17 +5668,17 @@ packages:
       }
     engines: { node: ">= 0.8.0" }
 
-  lilconfig@2.1.0:
+  lilconfig@3.1.2:
     resolution:
       {
-        integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==,
+        integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==,
       }
-    engines: { node: ">=10" }
+    engines: { node: ">=14" }
 
-  lilconfig@3.1.2:
+  lilconfig@3.1.3:
     resolution:
       {
-        integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==,
+        integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==,
       }
     engines: { node: ">=14" }
 
@@ -5603,10 +5688,10 @@ packages:
         integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==,
       }
 
-  lint-staged@15.2.10:
+  lint-staged@15.2.11:
     resolution:
       {
-        integrity: sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==,
+        integrity: sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==,
       }
     engines: { node: ">=18.12.0" }
     hasBin: true
@@ -5860,10 +5945,10 @@ packages:
     engines: { node: ">= 18" }
     hasBin: true
 
-  marked@14.1.3:
+  marked@15.0.4:
     resolution:
       {
-        integrity: sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==,
+        integrity: sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw==,
       }
     engines: { node: ">= 18" }
     hasBin: true
@@ -6721,10 +6806,10 @@ packages:
     peerDependencies:
       postcss: ^8.4.6
 
-  postcss-color-functional-notation@7.0.5:
+  postcss-color-functional-notation@7.0.6:
     resolution:
       {
-        integrity: sha512-zW97tq5t2sSSSZQcIS4y6NDZj79zVv8hrBIJ4PSFZFmMBcjYqCt8sRXFGIYZohCpfFHmimMNqJje2Qd3qqMNdg==,
+        integrity: sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -6918,10 +7003,10 @@ packages:
     peerDependencies:
       postcss: ^8.4.21
 
-  postcss-lab-function@7.0.5:
+  postcss-lab-function@7.0.6:
     resolution:
       {
-        integrity: sha512-q2M8CfQbjHxbwv1GPAny05EVuj0WByUgq/OWKgpfbTHnMchtUqsVQgaW1mztjSZ4UPufwuTLB14fmFGsoTE/VQ==,
+        integrity: sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -7005,15 +7090,6 @@ packages:
     peerDependencies:
       postcss: ^8.4.31
 
-  postcss-nested@5.0.6:
-    resolution:
-      {
-        integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==,
-      }
-    engines: { node: ">=12.0" }
-    peerDependencies:
-      postcss: ^8.2.14
-
   postcss-nested@6.2.0:
     resolution:
       {
@@ -7157,10 +7233,10 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
-  postcss-preset-env@10.0.9:
+  postcss-preset-env@10.1.2:
     resolution:
       {
-        integrity: sha512-mpfJWMAW6szov+ifW9HpNUUZE3BoXoHc4CDzNQHdH2I4CwsqulQ3bpFNUR6zh4tg0BUcqM7UUAbzG4UTel8QYw==,
+        integrity: sha512-OqUBZ9ByVfngWhMNuBEMy52Izj07oIFA6K/EOGBlaSv+P12MiE1+S2cqXtS1VuW82demQ/Tzc7typYk3uHunkA==,
       }
     engines: { node: ">=18" }
     peerDependencies:
@@ -7279,10 +7355,10 @@ packages:
         integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==,
       }
 
-  postcss@8.4.47:
+  postcss@8.4.49:
     resolution:
       {
-        integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==,
+        integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==,
       }
     engines: { node: ^10 || ^12 || >=14 }
 
@@ -7321,10 +7397,10 @@ packages:
     engines: { node: ">=10.13.0" }
     hasBin: true
 
-  prettier@3.3.3:
+  prettier@3.4.2:
     resolution:
       {
-        integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==,
+        integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==,
       }
     engines: { node: ">=14" }
     hasBin: true
@@ -8112,10 +8188,10 @@ packages:
     peerDependencies:
       stylelint: ^16.1.0
 
-  stylelint@16.10.0:
+  stylelint@16.12.0:
     resolution:
       {
-        integrity: sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==,
+        integrity: sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==,
       }
     engines: { node: ">=18.12.0" }
     hasBin: true
@@ -8184,17 +8260,17 @@ packages:
       }
     engines: { node: ^14.18.0 || >=16.0.0 }
 
-  table@6.8.2:
+  table@6.9.0:
     resolution:
       {
-        integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==,
+        integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==,
       }
     engines: { node: ">=10.0.0" }
 
-  tailwindcss@3.4.14:
+  tailwindcss@3.4.16:
     resolution:
       {
-        integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==,
+        integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==,
       }
     engines: { node: ">=14.0.0" }
     hasBin: true
@@ -8242,12 +8318,6 @@ packages:
       }
     engines: { node: ">=8" }
 
-  text-table@0.2.0:
-    resolution:
-      {
-        integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==,
-      }
-
   thenby@1.3.4:
     resolution:
       {
@@ -8441,22 +8511,20 @@ packages:
       }
     engines: { node: ">= 0.4" }
 
-  typescript-eslint@8.13.0:
+  typescript-eslint@8.18.1:
     resolution:
       {
-        integrity: sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==,
+        integrity: sha512-Mlaw6yxuaDEPQvb/2Qwu3/TfgeBHy9iTJ3mTwe7OvpPmF6KPQjVOfGyEJpPv6Ez2C34OODChhXrzYw/9phI0MQ==,
       }
     engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
     peerDependencies:
-      typescript: "*"
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: ">=4.8.4 <5.8.0"
 
-  typescript@5.5.4:
+  typescript@5.7.2:
     resolution:
       {
-        integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==,
+        integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==,
       }
     engines: { node: ">=14.17" }
     hasBin: true
@@ -8616,40 +8684,45 @@ packages:
         integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==,
       }
 
-  vite-plugin-pwa@0.20.5:
+  vite-plugin-pwa@0.21.1:
     resolution:
       {
-        integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==,
+        integrity: sha512-rkTbKFbd232WdiRJ9R3u+hZmf5SfQljX1b45NF6oLA6DSktEKpYllgTo1l2lkiZWMWV78pABJtFjNXfBef3/3Q==,
       }
     engines: { node: ">=16.0.0" }
     peerDependencies:
       "@vite-pwa/assets-generator": ^0.2.6
-      vite: ^3.1.0 || ^4.0.0 || ^5.0.0
-      workbox-build: ^7.1.0
-      workbox-window: ^7.1.0
+      vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+      workbox-build: ^7.3.0
+      workbox-window: ^7.3.0
     peerDependenciesMeta:
       "@vite-pwa/assets-generator":
         optional: true
 
-  vite@5.4.10:
+  vite@6.0.3:
     resolution:
       {
-        integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==,
+        integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==,
       }
-    engines: { node: ^18.0.0 || >=20.0.0 }
+    engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 }
     hasBin: true
     peerDependencies:
-      "@types/node": ^18.0.0 || >=20.0.0
+      "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: ">=1.21.0"
       less: "*"
       lightningcss: ^1.21.0
       sass: "*"
       sass-embedded: "*"
       stylus: "*"
       sugarss: "*"
-      terser: ^5.4.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
     peerDependenciesMeta:
       "@types/node":
         optional: true
+      jiti:
+        optional: true
       less:
         optional: true
       lightningcss:
@@ -8664,6 +8737,10 @@ packages:
         optional: true
       terser:
         optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
 
   w3c-keyname@2.2.8:
     resolution:
@@ -8671,10 +8748,10 @@ packages:
         integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==,
       }
 
-  wavesurfer.js@7.8.8:
+  wavesurfer.js@7.8.11:
     resolution:
       {
-        integrity: sha512-FNiJ6RCAu4TijRcwdaqlN19+Y+Hhqb7Nfo2P4LudzhNnQRMKvF3QmXd9+KHlQZrSjexeK3WbY4COsxgGJkxMag==,
+        integrity: sha512-bZs7A0vtTVOhuPoDGOXVevAIm+KVYBGwddjL9AeOS7kp/oPcVH9hQWQyR2rBAAfN6s0BKI+EdPEalkNaOmkA6A==,
       }
 
   wcwidth@1.0.1:
@@ -8938,18 +9015,18 @@ packages:
         integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==,
       }
 
-  yaml@2.5.1:
+  yaml@2.6.0:
     resolution:
       {
-        integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==,
+        integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==,
       }
     engines: { node: ">= 14" }
     hasBin: true
 
-  yaml@2.6.0:
+  yaml@2.6.1:
     resolution:
       {
-        integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==,
+        integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==,
       }
     engines: { node: ">= 14" }
     hasBin: true
@@ -9708,33 +9785,65 @@ snapshots:
       "@babel/helper-string-parser": 7.25.9
       "@babel/helper-validator-identifier": 7.25.9
 
-  "@codemirror/autocomplete@6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.2)(@lezer/common@1.2.3)":
+  "@codemirror/autocomplete@6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)":
     dependencies:
-      "@codemirror/language": 6.10.3
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/language": 6.10.7
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       "@lezer/common": 1.2.3
 
   "@codemirror/commands@6.7.1":
     dependencies:
-      "@codemirror/language": 6.10.3
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/language": 6.10.7
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       "@lezer/common": 1.2.3
 
+  "@codemirror/lang-css@6.3.1(@codemirror/view@6.35.3)":
+    dependencies:
+      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+      "@codemirror/language": 6.10.7
+      "@codemirror/state": 6.5.0
+      "@lezer/common": 1.2.3
+      "@lezer/css": 1.1.9
+    transitivePeerDependencies:
+      - "@codemirror/view"
+
+  "@codemirror/lang-html@6.4.9":
+    dependencies:
+      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+      "@codemirror/lang-css": 6.3.1(@codemirror/view@6.35.3)
+      "@codemirror/lang-javascript": 6.2.2
+      "@codemirror/language": 6.10.7
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
+      "@lezer/common": 1.2.3
+      "@lezer/css": 1.1.9
+      "@lezer/html": 1.3.10
+
+  "@codemirror/lang-javascript@6.2.2":
+    dependencies:
+      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+      "@codemirror/language": 6.10.7
+      "@codemirror/lint": 6.8.2
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
+      "@lezer/common": 1.2.3
+      "@lezer/javascript": 1.4.21
+
   "@codemirror/lang-xml@6.1.0":
     dependencies:
-      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.2)(@lezer/common@1.2.3)
-      "@codemirror/language": 6.10.3
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+      "@codemirror/language": 6.10.7
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       "@lezer/common": 1.2.3
       "@lezer/xml": 1.0.5
 
-  "@codemirror/language@6.10.3":
+  "@codemirror/language@6.10.7":
     dependencies:
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       "@lezer/common": 1.2.3
       "@lezer/highlight": 1.2.1
       "@lezer/lr": 1.4.2
@@ -9742,32 +9851,34 @@ snapshots:
 
   "@codemirror/lint@6.8.2":
     dependencies:
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       crelt: 1.0.6
 
   "@codemirror/search@6.5.7":
     dependencies:
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
       crelt: 1.0.6
 
-  "@codemirror/state@6.4.1": {}
+  "@codemirror/state@6.5.0":
+    dependencies:
+      "@marijn/find-cluster-break": 1.0.2
 
-  "@codemirror/view@6.34.2":
+  "@codemirror/view@6.35.3":
     dependencies:
-      "@codemirror/state": 6.4.1
+      "@codemirror/state": 6.5.0
       style-mod: 4.1.2
       w3c-keyname: 2.2.8
 
   "@colors/colors@1.5.0":
     optional: true
 
-  "@commitlint/cli@19.5.0(@types/node@22.9.0)(typescript@5.5.4)":
+  "@commitlint/cli@19.6.1(@types/node@22.9.0)(typescript@5.7.2)":
     dependencies:
       "@commitlint/format": 19.5.0
-      "@commitlint/lint": 19.5.0
-      "@commitlint/load": 19.5.0(@types/node@22.9.0)(typescript@5.5.4)
+      "@commitlint/lint": 19.6.0
+      "@commitlint/load": 19.6.1(@types/node@22.9.0)(typescript@5.7.2)
       "@commitlint/read": 19.5.0
       "@commitlint/types": 19.5.0
       tinyexec: 0.3.1
@@ -9776,7 +9887,7 @@ snapshots:
       - "@types/node"
       - typescript
 
-  "@commitlint/config-conventional@19.5.0":
+  "@commitlint/config-conventional@19.6.0":
     dependencies:
       "@commitlint/types": 19.5.0
       conventional-changelog-conventionalcommits: 7.0.2
@@ -9802,27 +9913,44 @@ snapshots:
       "@commitlint/types": 19.5.0
       chalk: 5.3.0
 
-  "@commitlint/is-ignored@19.5.0":
+  "@commitlint/is-ignored@19.6.0":
     dependencies:
       "@commitlint/types": 19.5.0
       semver: 7.6.3
 
-  "@commitlint/lint@19.5.0":
+  "@commitlint/lint@19.6.0":
     dependencies:
-      "@commitlint/is-ignored": 19.5.0
+      "@commitlint/is-ignored": 19.6.0
       "@commitlint/parse": 19.5.0
-      "@commitlint/rules": 19.5.0
+      "@commitlint/rules": 19.6.0
       "@commitlint/types": 19.5.0
 
-  "@commitlint/load@19.5.0(@types/node@22.9.0)(typescript@5.5.4)":
+  "@commitlint/load@19.5.0(@types/node@22.9.0)(typescript@5.7.2)":
     dependencies:
       "@commitlint/config-validator": 19.5.0
       "@commitlint/execute-rule": 19.5.0
       "@commitlint/resolve-extends": 19.5.0
       "@commitlint/types": 19.5.0
       chalk: 5.3.0
-      cosmiconfig: 9.0.0(typescript@5.5.4)
-      cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
+      cosmiconfig: 9.0.0(typescript@5.7.2)
+      cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
+      lodash.isplainobject: 4.0.6
+      lodash.merge: 4.6.2
+      lodash.uniq: 4.5.0
+    transitivePeerDependencies:
+      - "@types/node"
+      - typescript
+    optional: true
+
+  "@commitlint/load@19.6.1(@types/node@22.9.0)(typescript@5.7.2)":
+    dependencies:
+      "@commitlint/config-validator": 19.5.0
+      "@commitlint/execute-rule": 19.5.0
+      "@commitlint/resolve-extends": 19.5.0
+      "@commitlint/types": 19.5.0
+      chalk: 5.3.0
+      cosmiconfig: 9.0.0(typescript@5.7.2)
+      cosmiconfig-typescript-loader: 6.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -9855,7 +9983,7 @@ snapshots:
       lodash.mergewith: 4.6.2
       resolve-from: 5.0.0
 
-  "@commitlint/rules@19.5.0":
+  "@commitlint/rules@19.6.0":
     dependencies:
       "@commitlint/ensure": 19.5.0
       "@commitlint/message": 19.5.0
@@ -9880,15 +10008,15 @@ snapshots:
 
   "@csstools/color-helpers@5.0.1": {}
 
-  "@csstools/css-calc@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
+  "@csstools/css-calc@2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
 
-  "@csstools/css-color-parser@3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
+  "@csstools/css-color-parser@3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
     dependencies:
       "@csstools/color-helpers": 5.0.1
-      "@csstools/css-calc": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
 
@@ -9898,317 +10026,327 @@ snapshots:
 
   "@csstools/css-tokenizer@3.0.3": {}
 
-  "@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
-    dependencies:
-      "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
-      "@csstools/css-tokenizer": 3.0.3
-
   "@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)":
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
 
-  "@csstools/postcss-cascade-layers@5.0.1(postcss@8.4.47)":
+  "@csstools/postcss-cascade-layers@5.0.1(postcss@8.4.49)":
     dependencies:
       "@csstools/selector-specificity": 5.0.0(postcss-selector-parser@7.0.0)
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  "@csstools/postcss-color-function@4.0.5(postcss@8.4.47)":
+  "@csstools/postcss-color-function@4.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-color-mix-function@3.0.5(postcss@8.4.47)":
+  "@csstools/postcss-color-mix-function@3.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-content-alt-text@2.0.4(postcss@8.4.47)":
+  "@csstools/postcss-content-alt-text@2.0.4(postcss@8.4.49)":
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-exponential-functions@2.0.4(postcss@8.4.47)":
+  "@csstools/postcss-exponential-functions@2.0.5(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-calc": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-font-format-keywords@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-font-format-keywords@4.0.0(postcss@8.4.49)":
     dependencies:
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-gamut-mapping@2.0.5(postcss@8.4.47)":
+  "@csstools/postcss-gamut-mapping@2.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-gradients-interpolation-method@5.0.5(postcss@8.4.47)":
+  "@csstools/postcss-gradients-interpolation-method@5.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-hwb-function@4.0.5(postcss@8.4.47)":
+  "@csstools/postcss-hwb-function@4.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-ic-unit@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-ic-unit@4.0.0(postcss@8.4.49)":
     dependencies:
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-initial@2.0.0(postcss@8.4.47)":
+  "@csstools/postcss-initial@2.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.4.47)":
+  "@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.4.49)":
     dependencies:
       "@csstools/selector-specificity": 5.0.0(postcss-selector-parser@7.0.0)
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  "@csstools/postcss-light-dark-function@2.0.7(postcss@8.4.47)":
+  "@csstools/postcss-light-dark-function@2.0.7(postcss@8.4.49)":
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.4.47)":
+  "@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-logical-overflow@2.0.0(postcss@8.4.47)":
+  "@csstools/postcss-logical-overflow@2.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.4.47)":
+  "@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-logical-resize@3.0.0(postcss@8.4.47)":
+  "@csstools/postcss-logical-resize@3.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.4.47)":
+  "@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.4.49)":
     dependencies:
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-media-minmax@2.0.4(postcss@8.4.47)":
+  "@csstools/postcss-media-minmax@2.0.5(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-calc": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
       "@csstools/media-query-list-parser": 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.4.47)":
+  "@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.4.49)":
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
       "@csstools/media-query-list-parser": 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-nested-calc@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-nested-calc@4.0.0(postcss@8.4.49)":
     dependencies:
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-normalize-display-values@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-normalize-display-values@4.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-oklab-function@4.0.5(postcss@8.4.47)":
+  "@csstools/postcss-oklab-function@4.0.6(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  "@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-relative-color-syntax@3.0.5(postcss@8.4.47)":
+  "@csstools/postcss-random-function@1.0.1(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.4.47)":
+  "@csstools/postcss-relative-color-syntax@3.0.6(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-tokenizer": 3.0.3
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
+
+  "@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.4.49)":
+    dependencies:
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  "@csstools/postcss-stepped-value-functions@4.0.4(postcss@8.4.47)":
+  "@csstools/postcss-sign-functions@1.1.0(postcss@8.4.49)":
+    dependencies:
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-tokenizer": 3.0.3
+      postcss: 8.4.49
+
+  "@csstools/postcss-stepped-value-functions@4.0.5(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-calc": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-text-decoration-shorthand@4.0.1(postcss@8.4.47)":
+  "@csstools/postcss-text-decoration-shorthand@4.0.1(postcss@8.4.49)":
     dependencies:
       "@csstools/color-helpers": 5.0.1
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  "@csstools/postcss-trigonometric-functions@4.0.4(postcss@8.4.47)":
+  "@csstools/postcss-trigonometric-functions@4.0.5(postcss@8.4.49)":
     dependencies:
-      "@csstools/css-calc": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-calc": 2.1.0(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  "@csstools/postcss-unset-value@4.0.0(postcss@8.4.47)":
+  "@csstools/postcss-unset-value@4.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
   "@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.0.0)":
     dependencies:
       postcss-selector-parser: 7.0.0
 
-  "@csstools/selector-specificity@4.0.0(postcss-selector-parser@6.1.2)":
-    dependencies:
-      postcss-selector-parser: 6.1.2
-
   "@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.0.0)":
     dependencies:
       postcss-selector-parser: 7.0.0
 
-  "@csstools/utilities@2.0.0(postcss@8.4.47)":
+  "@csstools/utilities@2.0.0(postcss@8.4.49)":
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
   "@dual-bundle/import-meta-resolve@4.1.0": {}
 
-  "@esbuild/aix-ppc64@0.21.5":
+  "@esbuild/aix-ppc64@0.24.0":
     optional: true
 
-  "@esbuild/android-arm64@0.21.5":
+  "@esbuild/android-arm64@0.24.0":
     optional: true
 
-  "@esbuild/android-arm@0.21.5":
+  "@esbuild/android-arm@0.24.0":
     optional: true
 
-  "@esbuild/android-x64@0.21.5":
+  "@esbuild/android-x64@0.24.0":
     optional: true
 
-  "@esbuild/darwin-arm64@0.21.5":
+  "@esbuild/darwin-arm64@0.24.0":
     optional: true
 
-  "@esbuild/darwin-x64@0.21.5":
+  "@esbuild/darwin-x64@0.24.0":
     optional: true
 
-  "@esbuild/freebsd-arm64@0.21.5":
+  "@esbuild/freebsd-arm64@0.24.0":
     optional: true
 
-  "@esbuild/freebsd-x64@0.21.5":
+  "@esbuild/freebsd-x64@0.24.0":
     optional: true
 
-  "@esbuild/linux-arm64@0.21.5":
+  "@esbuild/linux-arm64@0.24.0":
     optional: true
 
-  "@esbuild/linux-arm@0.21.5":
+  "@esbuild/linux-arm@0.24.0":
     optional: true
 
-  "@esbuild/linux-ia32@0.21.5":
+  "@esbuild/linux-ia32@0.24.0":
     optional: true
 
-  "@esbuild/linux-loong64@0.21.5":
+  "@esbuild/linux-loong64@0.24.0":
     optional: true
 
-  "@esbuild/linux-mips64el@0.21.5":
+  "@esbuild/linux-mips64el@0.24.0":
     optional: true
 
-  "@esbuild/linux-ppc64@0.21.5":
+  "@esbuild/linux-ppc64@0.24.0":
     optional: true
 
-  "@esbuild/linux-riscv64@0.21.5":
+  "@esbuild/linux-riscv64@0.24.0":
     optional: true
 
-  "@esbuild/linux-s390x@0.21.5":
+  "@esbuild/linux-s390x@0.24.0":
     optional: true
 
-  "@esbuild/linux-x64@0.21.5":
+  "@esbuild/linux-x64@0.24.0":
     optional: true
 
-  "@esbuild/netbsd-x64@0.21.5":
+  "@esbuild/netbsd-x64@0.24.0":
     optional: true
 
-  "@esbuild/openbsd-x64@0.21.5":
+  "@esbuild/openbsd-arm64@0.24.0":
     optional: true
 
-  "@esbuild/sunos-x64@0.21.5":
+  "@esbuild/openbsd-x64@0.24.0":
     optional: true
 
-  "@esbuild/win32-arm64@0.21.5":
+  "@esbuild/sunos-x64@0.24.0":
     optional: true
 
-  "@esbuild/win32-ia32@0.21.5":
+  "@esbuild/win32-arm64@0.24.0":
     optional: true
 
-  "@esbuild/win32-x64@0.21.5":
+  "@esbuild/win32-ia32@0.24.0":
     optional: true
 
-  "@eslint-community/eslint-utils@4.4.1(eslint@9.14.0(jiti@1.21.6))":
+  "@esbuild/win32-x64@0.24.0":
+    optional: true
+
+  "@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@2.4.1))":
     dependencies:
-      eslint: 9.14.0(jiti@1.21.6)
+      eslint: 9.17.0(jiti@2.4.1)
       eslint-visitor-keys: 3.4.3
 
   "@eslint-community/regexpp@4.12.1": {}
 
-  "@eslint/config-array@0.18.0":
+  "@eslint/config-array@0.19.1":
     dependencies:
-      "@eslint/object-schema": 2.1.4
+      "@eslint/object-schema": 2.1.5
       debug: 4.3.7
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
 
-  "@eslint/core@0.7.0": {}
+  "@eslint/core@0.9.1":
+    dependencies:
+      "@types/json-schema": 7.0.15
 
-  "@eslint/eslintrc@3.1.0":
+  "@eslint/eslintrc@3.2.0":
     dependencies:
       ajv: 6.12.6
       debug: 4.3.7
@@ -10222,11 +10360,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  "@eslint/js@9.14.0": {}
+  "@eslint/js@9.17.0": {}
 
-  "@eslint/object-schema@2.1.4": {}
+  "@eslint/object-schema@2.1.5": {}
 
-  "@eslint/plugin-kit@0.2.2":
+  "@eslint/plugin-kit@0.2.4":
     dependencies:
       levn: 0.4.1
 
@@ -10273,7 +10411,7 @@ snapshots:
 
   "@github/markdown-toolbar-element@2.2.3": {}
 
-  "@github/relative-time-element@4.4.3": {}
+  "@github/relative-time-element@4.4.4": {}
 
   "@humanfs/core@0.19.1": {}
 
@@ -10321,10 +10459,28 @@ snapshots:
 
   "@lezer/common@1.2.3": {}
 
+  "@lezer/css@1.1.9":
+    dependencies:
+      "@lezer/common": 1.2.3
+      "@lezer/highlight": 1.2.1
+      "@lezer/lr": 1.4.2
+
   "@lezer/highlight@1.2.1":
     dependencies:
       "@lezer/common": 1.2.3
 
+  "@lezer/html@1.3.10":
+    dependencies:
+      "@lezer/common": 1.2.3
+      "@lezer/highlight": 1.2.1
+      "@lezer/lr": 1.4.2
+
+  "@lezer/javascript@1.4.21":
+    dependencies:
+      "@lezer/common": 1.2.3
+      "@lezer/highlight": 1.2.1
+      "@lezer/lr": 1.4.2
+
   "@lezer/lr@1.4.2":
     dependencies:
       "@lezer/common": 1.2.3
@@ -10345,6 +10501,8 @@ snapshots:
     dependencies:
       "@lit-labs/ssr-dom-shim": 1.2.1
 
+  "@marijn/find-cluster-break@1.0.2": {}
+
   "@nodelib/fs.scandir@2.1.5":
     dependencies:
       "@nodelib/fs.stat": 2.0.5
@@ -10552,15 +10710,15 @@ snapshots:
 
   "@sec-ant/readable-stream@0.4.1": {}
 
-  "@semantic-release/changelog@6.0.3(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/changelog@6.0.3(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@semantic-release/error": 3.0.0
       aggregate-error: 3.1.0
       fs-extra: 11.2.0
       lodash: 4.17.21
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
 
-  "@semantic-release/commit-analyzer@13.0.0(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/commit-analyzer@13.0.0(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       conventional-changelog-angular: 8.0.0
       conventional-changelog-writer: 8.0.0
@@ -10570,7 +10728,7 @@ snapshots:
       import-from-esm: 1.3.4
       lodash-es: 4.17.21
       micromatch: 4.0.8
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
     transitivePeerDependencies:
       - supports-color
 
@@ -10578,7 +10736,7 @@ snapshots:
 
   "@semantic-release/error@4.0.0": {}
 
-  "@semantic-release/exec@6.0.3(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/exec@6.0.3(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@semantic-release/error": 3.0.0
       aggregate-error: 3.1.0
@@ -10586,11 +10744,11 @@ snapshots:
       execa: 5.1.1
       lodash: 4.17.21
       parse-json: 5.2.0
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
     transitivePeerDependencies:
       - supports-color
 
-  "@semantic-release/git@10.0.1(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/git@10.0.1(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@semantic-release/error": 3.0.0
       aggregate-error: 3.1.0
@@ -10600,11 +10758,11 @@ snapshots:
       lodash: 4.17.21
       micromatch: 4.0.8
       p-reduce: 2.1.0
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
     transitivePeerDependencies:
       - supports-color
 
-  "@semantic-release/github@11.0.0(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/github@11.0.0(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@octokit/core": 6.1.2
       "@octokit/plugin-paginate-rest": 11.3.5(@octokit/core@6.1.2)
@@ -10621,12 +10779,12 @@ snapshots:
       lodash-es: 4.17.21
       mime: 4.0.4
       p-filter: 4.1.0
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
       url-join: 5.0.0
     transitivePeerDependencies:
       - supports-color
 
-  "@semantic-release/gitlab@13.2.1(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/gitlab@13.2.3(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@semantic-release/error": 4.0.0
       aggregate-error: 5.0.0
@@ -10640,12 +10798,12 @@ snapshots:
       hpagent: 1.2.0
       lodash-es: 4.17.21
       parse-url: 9.2.0
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
       url-join: 4.0.1
     transitivePeerDependencies:
       - supports-color
 
-  "@semantic-release/npm@12.0.1(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/npm@12.0.1(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       "@semantic-release/error": 4.0.0
       aggregate-error: 5.0.0
@@ -10658,11 +10816,11 @@ snapshots:
       rc: 1.2.8
       read-pkg: 9.0.1
       registry-auth-token: 5.0.2
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
       semver: 7.6.3
       tempy: 3.1.0
 
-  "@semantic-release/release-notes-generator@14.0.1(semantic-release@24.2.0(typescript@5.5.4))":
+  "@semantic-release/release-notes-generator@14.0.1(semantic-release@24.2.0(typescript@5.7.2))":
     dependencies:
       conventional-changelog-angular: 8.0.0
       conventional-changelog-writer: 8.0.0
@@ -10674,7 +10832,7 @@ snapshots:
       into-stream: 7.0.0
       lodash-es: 4.17.21
       read-package-up: 11.0.0
-      semantic-release: 24.2.0(typescript@5.5.4)
+      semantic-release: 24.2.0(typescript@5.7.2)
     transitivePeerDependencies:
       - supports-color
 
@@ -10699,23 +10857,18 @@ snapshots:
     dependencies:
       defer-to-connect: 2.0.1
 
-  "@tailwindcss/forms@0.5.9(tailwindcss@3.4.14)":
+  "@tailwindcss/forms@0.5.9(tailwindcss@3.4.16)":
     dependencies:
       mini-svg-data-uri: 1.4.4
-      tailwindcss: 3.4.14
+      tailwindcss: 3.4.16
 
-  "@tailwindcss/nesting@0.0.0-insiders.565cd3e(postcss@8.4.47)":
-    dependencies:
-      postcss: 8.4.47
-      postcss-nested: 5.0.6(postcss@8.4.47)
-
-  "@tailwindcss/typography@0.5.15(tailwindcss@3.4.14)":
+  "@tailwindcss/typography@0.5.15(tailwindcss@3.4.16)":
     dependencies:
       lodash.castarray: 4.4.0
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       postcss-selector-parser: 6.0.10
-      tailwindcss: 3.4.14
+      tailwindcss: 3.4.16
 
   "@trysound/sax@0.2.0": {}
 
@@ -10744,7 +10897,7 @@ snapshots:
 
   "@types/json-schema@7.0.15": {}
 
-  "@types/leaflet@1.9.14":
+  "@types/leaflet@1.9.15":
     dependencies:
       "@types/geojson": 7946.0.14
 
@@ -10762,86 +10915,82 @@ snapshots:
 
   "@types/trusted-types@2.0.7": {}
 
-  "@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)":
+  "@typescript-eslint/eslint-plugin@8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)":
     dependencies:
       "@eslint-community/regexpp": 4.12.1
-      "@typescript-eslint/parser": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      "@typescript-eslint/scope-manager": 8.13.0
-      "@typescript-eslint/type-utils": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      "@typescript-eslint/utils": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      "@typescript-eslint/visitor-keys": 8.13.0
-      eslint: 9.14.0(jiti@1.21.6)
+      "@typescript-eslint/parser": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      "@typescript-eslint/scope-manager": 8.18.1
+      "@typescript-eslint/type-utils": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      "@typescript-eslint/utils": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      "@typescript-eslint/visitor-keys": 8.18.1
+      eslint: 9.17.0(jiti@2.4.1)
       graphemer: 1.4.0
       ignore: 5.3.2
       natural-compare: 1.4.0
-      ts-api-utils: 1.4.0(typescript@5.5.4)
-    optionalDependencies:
-      typescript: 5.5.4
+      ts-api-utils: 1.4.0(typescript@5.7.2)
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  "@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)":
+  "@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)":
     dependencies:
-      "@typescript-eslint/scope-manager": 8.13.0
-      "@typescript-eslint/types": 8.13.0
-      "@typescript-eslint/typescript-estree": 8.13.0(typescript@5.5.4)
-      "@typescript-eslint/visitor-keys": 8.13.0
-      debug: 4.3.7
-      eslint: 9.14.0(jiti@1.21.6)
-    optionalDependencies:
-      typescript: 5.5.4
+      "@typescript-eslint/scope-manager": 8.18.1
+      "@typescript-eslint/types": 8.18.1
+      "@typescript-eslint/typescript-estree": 8.18.1(typescript@5.7.2)
+      "@typescript-eslint/visitor-keys": 8.18.1
+      debug: 4.4.0
+      eslint: 9.17.0(jiti@2.4.1)
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  "@typescript-eslint/scope-manager@8.13.0":
+  "@typescript-eslint/scope-manager@8.18.1":
     dependencies:
-      "@typescript-eslint/types": 8.13.0
-      "@typescript-eslint/visitor-keys": 8.13.0
+      "@typescript-eslint/types": 8.18.1
+      "@typescript-eslint/visitor-keys": 8.18.1
 
-  "@typescript-eslint/type-utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)":
+  "@typescript-eslint/type-utils@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)":
     dependencies:
-      "@typescript-eslint/typescript-estree": 8.13.0(typescript@5.5.4)
-      "@typescript-eslint/utils": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      debug: 4.3.7
-      ts-api-utils: 1.4.0(typescript@5.5.4)
-    optionalDependencies:
-      typescript: 5.5.4
+      "@typescript-eslint/typescript-estree": 8.18.1(typescript@5.7.2)
+      "@typescript-eslint/utils": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      debug: 4.4.0
+      eslint: 9.17.0(jiti@2.4.1)
+      ts-api-utils: 1.4.0(typescript@5.7.2)
+      typescript: 5.7.2
     transitivePeerDependencies:
-      - eslint
       - supports-color
 
-  "@typescript-eslint/types@8.13.0": {}
+  "@typescript-eslint/types@8.18.1": {}
 
-  "@typescript-eslint/typescript-estree@8.13.0(typescript@5.5.4)":
+  "@typescript-eslint/typescript-estree@8.18.1(typescript@5.7.2)":
     dependencies:
-      "@typescript-eslint/types": 8.13.0
-      "@typescript-eslint/visitor-keys": 8.13.0
-      debug: 4.3.7
+      "@typescript-eslint/types": 8.18.1
+      "@typescript-eslint/visitor-keys": 8.18.1
+      debug: 4.4.0
       fast-glob: 3.3.2
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.4.0(typescript@5.5.4)
-    optionalDependencies:
-      typescript: 5.5.4
+      ts-api-utils: 1.4.0(typescript@5.7.2)
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  "@typescript-eslint/utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)":
+  "@typescript-eslint/utils@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)":
     dependencies:
-      "@eslint-community/eslint-utils": 4.4.1(eslint@9.14.0(jiti@1.21.6))
-      "@typescript-eslint/scope-manager": 8.13.0
-      "@typescript-eslint/types": 8.13.0
-      "@typescript-eslint/typescript-estree": 8.13.0(typescript@5.5.4)
-      eslint: 9.14.0(jiti@1.21.6)
+      "@eslint-community/eslint-utils": 4.4.1(eslint@9.17.0(jiti@2.4.1))
+      "@typescript-eslint/scope-manager": 8.18.1
+      "@typescript-eslint/types": 8.18.1
+      "@typescript-eslint/typescript-estree": 8.18.1(typescript@5.7.2)
+      eslint: 9.17.0(jiti@2.4.1)
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
-      - typescript
 
-  "@typescript-eslint/visitor-keys@8.13.0":
+  "@typescript-eslint/visitor-keys@8.18.1":
     dependencies:
-      "@typescript-eslint/types": 8.13.0
-      eslint-visitor-keys: 3.4.3
+      "@typescript-eslint/types": 8.18.1
+      eslint-visitor-keys: 4.2.0
 
   "@vime/core@5.4.1":
     dependencies:
@@ -10970,14 +11119,14 @@ snapshots:
 
   at-least-node@1.0.0: {}
 
-  autoprefixer@10.4.20(postcss@8.4.47):
+  autoprefixer@10.4.20(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
       caniuse-lite: 1.0.30001677
       fraction.js: 4.3.7
       normalize-range: 0.1.2
       picocolors: 1.1.1
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
   available-typed-arrays@1.0.7:
@@ -11131,7 +11280,7 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
-  ci-info@3.9.0: {}
+  ci-info@4.1.0: {}
 
   clean-stack@2.2.0: {}
 
@@ -11193,13 +11342,13 @@ snapshots:
 
   codemirror@6.0.1(@lezer/common@1.2.3):
     dependencies:
-      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.2)(@lezer/common@1.2.3)
+      "@codemirror/autocomplete": 6.18.2(@codemirror/language@6.10.7)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
       "@codemirror/commands": 6.7.1
-      "@codemirror/language": 6.10.3
+      "@codemirror/language": 6.10.7
       "@codemirror/lint": 6.8.2
       "@codemirror/search": 6.5.7
-      "@codemirror/state": 6.4.1
-      "@codemirror/view": 6.34.2
+      "@codemirror/state": 6.5.0
+      "@codemirror/view": 6.35.3
     transitivePeerDependencies:
       - "@lezer/common"
 
@@ -11227,10 +11376,10 @@ snapshots:
 
   commander@7.2.0: {}
 
-  commitizen@4.3.1(@types/node@22.9.0)(typescript@5.5.4):
+  commitizen@4.3.1(@types/node@22.9.0)(typescript@5.7.2):
     dependencies:
       cachedir: 2.3.0
-      cz-conventional-changelog: 3.3.0(@types/node@22.9.0)(typescript@5.5.4)
+      cz-conventional-changelog: 3.3.0(@types/node@22.9.0)(typescript@5.7.2)
       dedent: 0.7.0
       detect-indent: 6.1.0
       find-node-modules: 2.1.3
@@ -11308,21 +11457,29 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
+  cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
     dependencies:
       "@types/node": 22.9.0
-      cosmiconfig: 9.0.0(typescript@5.5.4)
+      cosmiconfig: 9.0.0(typescript@5.7.2)
       jiti: 1.21.6
-      typescript: 5.5.4
+      typescript: 5.7.2
+    optional: true
 
-  cosmiconfig@9.0.0(typescript@5.5.4):
+  cosmiconfig-typescript-loader@6.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
+    dependencies:
+      "@types/node": 22.9.0
+      cosmiconfig: 9.0.0(typescript@5.7.2)
+      jiti: 2.4.1
+      typescript: 5.7.2
+
+  cosmiconfig@9.0.0(typescript@5.7.2):
     dependencies:
       env-paths: 2.2.1
       import-fresh: 3.3.0
       js-yaml: 4.1.0
       parse-json: 5.2.0
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.7.2
 
   crelt@1.0.6: {}
 
@@ -11336,6 +11493,12 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
   crypto-js@4.2.0: {}
 
   crypto-random-string@2.0.0: {}
@@ -11344,27 +11507,27 @@ snapshots:
     dependencies:
       type-fest: 1.4.0
 
-  css-blank-pseudo@7.0.1(postcss@8.4.47):
+  css-blank-pseudo@7.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  css-declaration-sorter@7.2.0(postcss@8.4.47):
+  css-declaration-sorter@7.2.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
   css-functions-list@3.2.3: {}
 
-  css-has-pseudo@7.0.1(postcss@8.4.47):
+  css-has-pseudo@7.0.2(postcss@8.4.49):
     dependencies:
       "@csstools/selector-specificity": 5.0.0(postcss-selector-parser@7.0.0)
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
       postcss-value-parser: 4.2.0
 
-  css-prefers-color-scheme@10.0.0(postcss@8.4.47):
+  css-prefers-color-scheme@10.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
   css-select@5.1.0:
     dependencies:
@@ -11391,68 +11554,68 @@ snapshots:
 
   css-what@6.1.0: {}
 
-  cssdb@8.1.2: {}
+  cssdb@8.2.3: {}
 
   cssesc@3.0.0: {}
 
-  cssnano-preset-default@7.0.6(postcss@8.4.47):
+  cssnano-preset-default@7.0.6(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      css-declaration-sorter: 7.2.0(postcss@8.4.47)
-      cssnano-utils: 5.0.0(postcss@8.4.47)
-      postcss: 8.4.47
-      postcss-calc: 10.0.2(postcss@8.4.47)
-      postcss-colormin: 7.0.2(postcss@8.4.47)
-      postcss-convert-values: 7.0.4(postcss@8.4.47)
-      postcss-discard-comments: 7.0.3(postcss@8.4.47)
-      postcss-discard-duplicates: 7.0.1(postcss@8.4.47)
-      postcss-discard-empty: 7.0.0(postcss@8.4.47)
-      postcss-discard-overridden: 7.0.0(postcss@8.4.47)
-      postcss-merge-longhand: 7.0.4(postcss@8.4.47)
-      postcss-merge-rules: 7.0.4(postcss@8.4.47)
-      postcss-minify-font-values: 7.0.0(postcss@8.4.47)
-      postcss-minify-gradients: 7.0.0(postcss@8.4.47)
-      postcss-minify-params: 7.0.2(postcss@8.4.47)
-      postcss-minify-selectors: 7.0.4(postcss@8.4.47)
-      postcss-normalize-charset: 7.0.0(postcss@8.4.47)
-      postcss-normalize-display-values: 7.0.0(postcss@8.4.47)
-      postcss-normalize-positions: 7.0.0(postcss@8.4.47)
-      postcss-normalize-repeat-style: 7.0.0(postcss@8.4.47)
-      postcss-normalize-string: 7.0.0(postcss@8.4.47)
-      postcss-normalize-timing-functions: 7.0.0(postcss@8.4.47)
-      postcss-normalize-unicode: 7.0.2(postcss@8.4.47)
-      postcss-normalize-url: 7.0.0(postcss@8.4.47)
-      postcss-normalize-whitespace: 7.0.0(postcss@8.4.47)
-      postcss-ordered-values: 7.0.1(postcss@8.4.47)
-      postcss-reduce-initial: 7.0.2(postcss@8.4.47)
-      postcss-reduce-transforms: 7.0.0(postcss@8.4.47)
-      postcss-svgo: 7.0.1(postcss@8.4.47)
-      postcss-unique-selectors: 7.0.3(postcss@8.4.47)
-
-  cssnano-utils@5.0.0(postcss@8.4.47):
-    dependencies:
-      postcss: 8.4.47
-
-  cssnano@7.0.6(postcss@8.4.47):
-    dependencies:
-      cssnano-preset-default: 7.0.6(postcss@8.4.47)
+      css-declaration-sorter: 7.2.0(postcss@8.4.49)
+      cssnano-utils: 5.0.0(postcss@8.4.49)
+      postcss: 8.4.49
+      postcss-calc: 10.0.2(postcss@8.4.49)
+      postcss-colormin: 7.0.2(postcss@8.4.49)
+      postcss-convert-values: 7.0.4(postcss@8.4.49)
+      postcss-discard-comments: 7.0.3(postcss@8.4.49)
+      postcss-discard-duplicates: 7.0.1(postcss@8.4.49)
+      postcss-discard-empty: 7.0.0(postcss@8.4.49)
+      postcss-discard-overridden: 7.0.0(postcss@8.4.49)
+      postcss-merge-longhand: 7.0.4(postcss@8.4.49)
+      postcss-merge-rules: 7.0.4(postcss@8.4.49)
+      postcss-minify-font-values: 7.0.0(postcss@8.4.49)
+      postcss-minify-gradients: 7.0.0(postcss@8.4.49)
+      postcss-minify-params: 7.0.2(postcss@8.4.49)
+      postcss-minify-selectors: 7.0.4(postcss@8.4.49)
+      postcss-normalize-charset: 7.0.0(postcss@8.4.49)
+      postcss-normalize-display-values: 7.0.0(postcss@8.4.49)
+      postcss-normalize-positions: 7.0.0(postcss@8.4.49)
+      postcss-normalize-repeat-style: 7.0.0(postcss@8.4.49)
+      postcss-normalize-string: 7.0.0(postcss@8.4.49)
+      postcss-normalize-timing-functions: 7.0.0(postcss@8.4.49)
+      postcss-normalize-unicode: 7.0.2(postcss@8.4.49)
+      postcss-normalize-url: 7.0.0(postcss@8.4.49)
+      postcss-normalize-whitespace: 7.0.0(postcss@8.4.49)
+      postcss-ordered-values: 7.0.1(postcss@8.4.49)
+      postcss-reduce-initial: 7.0.2(postcss@8.4.49)
+      postcss-reduce-transforms: 7.0.0(postcss@8.4.49)
+      postcss-svgo: 7.0.1(postcss@8.4.49)
+      postcss-unique-selectors: 7.0.3(postcss@8.4.49)
+
+  cssnano-utils@5.0.0(postcss@8.4.49):
+    dependencies:
+      postcss: 8.4.49
+
+  cssnano@7.0.6(postcss@8.4.49):
+    dependencies:
+      cssnano-preset-default: 7.0.6(postcss@8.4.49)
       lilconfig: 3.1.2
-      postcss: 8.4.47
+      postcss: 8.4.49
 
   csso@5.0.5:
     dependencies:
       css-tree: 2.2.1
 
-  cz-conventional-changelog@3.3.0(@types/node@22.9.0)(typescript@5.5.4):
+  cz-conventional-changelog@3.3.0(@types/node@22.9.0)(typescript@5.7.2):
     dependencies:
       chalk: 2.4.2
-      commitizen: 4.3.1(@types/node@22.9.0)(typescript@5.5.4)
+      commitizen: 4.3.1(@types/node@22.9.0)(typescript@5.7.2)
       conventional-commit-types: 3.0.0
       lodash.map: 4.6.0
       longest: 2.0.1
       word-wrap: 1.2.5
     optionalDependencies:
-      "@commitlint/load": 19.5.0(@types/node@22.9.0)(typescript@5.5.4)
+      "@commitlint/load": 19.5.0(@types/node@22.9.0)(typescript@5.7.2)
     transitivePeerDependencies:
       - "@types/node"
       - typescript
@@ -11526,6 +11689,10 @@ snapshots:
     dependencies:
       ms: 2.1.3
 
+  debug@4.4.0:
+    dependencies:
+      ms: 2.1.3
+
   decamelize@1.2.0: {}
 
   decompress-response@6.0.0:
@@ -11709,31 +11876,32 @@ snapshots:
       is-date-object: 1.0.5
       is-symbol: 1.0.4
 
-  esbuild@0.21.5:
+  esbuild@0.24.0:
     optionalDependencies:
-      "@esbuild/aix-ppc64": 0.21.5
-      "@esbuild/android-arm": 0.21.5
-      "@esbuild/android-arm64": 0.21.5
-      "@esbuild/android-x64": 0.21.5
-      "@esbuild/darwin-arm64": 0.21.5
-      "@esbuild/darwin-x64": 0.21.5
-      "@esbuild/freebsd-arm64": 0.21.5
-      "@esbuild/freebsd-x64": 0.21.5
-      "@esbuild/linux-arm": 0.21.5
-      "@esbuild/linux-arm64": 0.21.5
-      "@esbuild/linux-ia32": 0.21.5
-      "@esbuild/linux-loong64": 0.21.5
-      "@esbuild/linux-mips64el": 0.21.5
-      "@esbuild/linux-ppc64": 0.21.5
-      "@esbuild/linux-riscv64": 0.21.5
-      "@esbuild/linux-s390x": 0.21.5
-      "@esbuild/linux-x64": 0.21.5
-      "@esbuild/netbsd-x64": 0.21.5
-      "@esbuild/openbsd-x64": 0.21.5
-      "@esbuild/sunos-x64": 0.21.5
-      "@esbuild/win32-arm64": 0.21.5
-      "@esbuild/win32-ia32": 0.21.5
-      "@esbuild/win32-x64": 0.21.5
+      "@esbuild/aix-ppc64": 0.24.0
+      "@esbuild/android-arm": 0.24.0
+      "@esbuild/android-arm64": 0.24.0
+      "@esbuild/android-x64": 0.24.0
+      "@esbuild/darwin-arm64": 0.24.0
+      "@esbuild/darwin-x64": 0.24.0
+      "@esbuild/freebsd-arm64": 0.24.0
+      "@esbuild/freebsd-x64": 0.24.0
+      "@esbuild/linux-arm": 0.24.0
+      "@esbuild/linux-arm64": 0.24.0
+      "@esbuild/linux-ia32": 0.24.0
+      "@esbuild/linux-loong64": 0.24.0
+      "@esbuild/linux-mips64el": 0.24.0
+      "@esbuild/linux-ppc64": 0.24.0
+      "@esbuild/linux-riscv64": 0.24.0
+      "@esbuild/linux-s390x": 0.24.0
+      "@esbuild/linux-x64": 0.24.0
+      "@esbuild/netbsd-x64": 0.24.0
+      "@esbuild/openbsd-arm64": 0.24.0
+      "@esbuild/openbsd-x64": 0.24.0
+      "@esbuild/sunos-x64": 0.24.0
+      "@esbuild/win32-arm64": 0.24.0
+      "@esbuild/win32-ia32": 0.24.0
+      "@esbuild/win32-x64": 0.24.0
 
   escalade@3.2.0: {}
 
@@ -11743,19 +11911,19 @@ snapshots:
 
   escape-string-regexp@5.0.0: {}
 
-  eslint-config-prettier@9.1.0(eslint@9.14.0(jiti@1.21.6)):
+  eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.1)):
     dependencies:
-      eslint: 9.14.0(jiti@1.21.6)
+      eslint: 9.17.0(jiti@2.4.1)
 
-  eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6))(prettier@3.3.3):
+  eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.1)))(eslint@9.17.0(jiti@2.4.1))(prettier@3.4.2):
     dependencies:
-      eslint: 9.14.0(jiti@1.21.6)
-      prettier: 3.3.3
+      eslint: 9.17.0(jiti@2.4.1)
+      prettier: 3.4.2
       prettier-linter-helpers: 1.0.0
       synckit: 0.9.2
     optionalDependencies:
       "@types/eslint": 9.6.1
-      eslint-config-prettier: 9.1.0(eslint@9.14.0(jiti@1.21.6))
+      eslint-config-prettier: 9.1.0(eslint@9.17.0(jiti@2.4.1))
 
   eslint-scope@8.2.0:
     dependencies:
@@ -11766,15 +11934,15 @@ snapshots:
 
   eslint-visitor-keys@4.2.0: {}
 
-  eslint@9.14.0(jiti@1.21.6):
+  eslint@9.17.0(jiti@2.4.1):
     dependencies:
-      "@eslint-community/eslint-utils": 4.4.1(eslint@9.14.0(jiti@1.21.6))
+      "@eslint-community/eslint-utils": 4.4.1(eslint@9.17.0(jiti@2.4.1))
       "@eslint-community/regexpp": 4.12.1
-      "@eslint/config-array": 0.18.0
-      "@eslint/core": 0.7.0
-      "@eslint/eslintrc": 3.1.0
-      "@eslint/js": 9.14.0
-      "@eslint/plugin-kit": 0.2.2
+      "@eslint/config-array": 0.19.1
+      "@eslint/core": 0.9.1
+      "@eslint/eslintrc": 3.2.0
+      "@eslint/js": 9.17.0
+      "@eslint/plugin-kit": 0.2.4
       "@humanfs/node": 0.16.6
       "@humanwhocodes/module-importer": 1.0.1
       "@humanwhocodes/retry": 0.4.1
@@ -11782,7 +11950,7 @@ snapshots:
       "@types/json-schema": 7.0.15
       ajv: 6.12.6
       chalk: 4.1.2
-      cross-spawn: 7.0.3
+      cross-spawn: 7.0.6
       debug: 4.3.7
       escape-string-regexp: 4.0.0
       eslint-scope: 8.2.0
@@ -11802,9 +11970,8 @@ snapshots:
       minimatch: 3.1.2
       natural-compare: 1.4.0
       optionator: 0.9.4
-      text-table: 0.2.0
     optionalDependencies:
-      jiti: 1.21.6
+      jiti: 2.4.1
     transitivePeerDependencies:
       - supports-color
 
@@ -12153,7 +12320,7 @@ snapshots:
 
   globals@14.0.0: {}
 
-  globals@15.12.0: {}
+  globals@15.13.0: {}
 
   globalthis@1.0.4:
     dependencies:
@@ -12255,6 +12422,8 @@ snapshots:
 
   html-tags@3.3.1: {}
 
+  htmlfy@0.5.0: {}
+
   http-cache-semantics@4.1.1: {}
 
   http-proxy-agent@7.0.2:
@@ -12282,7 +12451,7 @@ snapshots:
 
   human-signals@8.0.0: {}
 
-  husky@9.1.6: {}
+  husky@9.1.7: {}
 
   iconv-lite@0.4.24:
     dependencies:
@@ -12407,9 +12576,9 @@ snapshots:
 
   is-callable@1.2.7: {}
 
-  is-ci@3.0.1:
+  is-ci@4.1.0:
     dependencies:
-      ci-info: 3.9.0
+      ci-info: 4.1.0
 
   is-core-module@2.15.1:
     dependencies:
@@ -12533,6 +12702,8 @@ snapshots:
 
   jiti@1.21.6: {}
 
+  jiti@2.4.1: {}
+
   jpeg-exif@1.1.4: {}
 
   js-tokens@4.0.0: {}
@@ -12581,7 +12752,7 @@ snapshots:
 
   kind-of@6.0.3: {}
 
-  known-css-properties@0.34.0: {}
+  known-css-properties@0.35.0: {}
 
   leaflet.markercluster@1.5.3(leaflet@1.9.4):
     dependencies:
@@ -12596,24 +12767,24 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  lilconfig@2.1.0: {}
-
   lilconfig@3.1.2: {}
 
+  lilconfig@3.1.3: {}
+
   lines-and-columns@1.2.4: {}
 
-  lint-staged@15.2.10:
+  lint-staged@15.2.11:
     dependencies:
       chalk: 5.3.0
       commander: 12.1.0
-      debug: 4.3.7
+      debug: 4.4.0
       execa: 8.0.1
-      lilconfig: 3.1.2
+      lilconfig: 3.1.3
       listr2: 8.2.5
       micromatch: 4.0.8
       pidtree: 0.6.0
       string-argv: 0.3.2
-      yaml: 2.5.1
+      yaml: 2.6.1
     transitivePeerDependencies:
       - supports-color
 
@@ -12748,7 +12919,7 @@ snapshots:
 
   marked@12.0.2: {}
 
-  marked@14.1.3: {}
+  marked@15.0.4: {}
 
   mathml-tag-names@2.1.3: {}
 
@@ -13090,400 +13261,397 @@ snapshots:
 
   possible-typed-array-names@1.0.0: {}
 
-  postcss-attribute-case-insensitive@7.0.1(postcss@8.4.47):
+  postcss-attribute-case-insensitive@7.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-calc@10.0.2(postcss@8.4.47):
+  postcss-calc@10.0.2(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
       postcss-value-parser: 4.2.0
 
-  postcss-clamp@4.1.0(postcss@8.4.47):
+  postcss-clamp@4.1.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-color-functional-notation@7.0.5(postcss@8.4.47):
+  postcss-color-functional-notation@7.0.6(postcss@8.4.49):
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  postcss-color-hex-alpha@10.0.0(postcss@8.4.47):
+  postcss-color-hex-alpha@10.0.0(postcss@8.4.49):
     dependencies:
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-color-rebeccapurple@10.0.0(postcss@8.4.47):
+  postcss-color-rebeccapurple@10.0.0(postcss@8.4.49):
     dependencies:
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-colormin@7.0.2(postcss@8.4.47):
+  postcss-colormin@7.0.2(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
       caniuse-api: 3.0.0
       colord: 2.9.3
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-convert-values@7.0.4(postcss@8.4.47):
+  postcss-convert-values@7.0.4(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-custom-media@11.0.5(postcss@8.4.47):
+  postcss-custom-media@11.0.5(postcss@8.4.49):
     dependencies:
       "@csstools/cascade-layer-name-parser": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
       "@csstools/media-query-list-parser": 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-custom-properties@14.0.4(postcss@8.4.47):
+  postcss-custom-properties@14.0.4(postcss@8.4.49):
     dependencies:
       "@csstools/cascade-layer-name-parser": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-custom-selectors@8.0.4(postcss@8.4.47):
+  postcss-custom-selectors@8.0.4(postcss@8.4.49):
     dependencies:
       "@csstools/cascade-layer-name-parser": 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-dir-pseudo-class@9.0.1(postcss@8.4.47):
+  postcss-dir-pseudo-class@9.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-discard-comments@7.0.3(postcss@8.4.47):
+  postcss-discard-comments@7.0.3(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
-  postcss-discard-duplicates@7.0.1(postcss@8.4.47):
+  postcss-discard-duplicates@7.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-discard-empty@7.0.0(postcss@8.4.47):
+  postcss-discard-empty@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-discard-overridden@7.0.0(postcss@8.4.47):
+  postcss-discard-overridden@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-double-position-gradients@6.0.0(postcss@8.4.47):
+  postcss-double-position-gradients@6.0.0(postcss@8.4.49):
     dependencies:
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-focus-visible@10.0.1(postcss@8.4.47):
+  postcss-focus-visible@10.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-focus-within@9.0.1(postcss@8.4.47):
+  postcss-focus-within@9.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-font-variant@5.0.0(postcss@8.4.47):
+  postcss-font-variant@5.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-gap-properties@6.0.0(postcss@8.4.47):
+  postcss-gap-properties@6.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-image-set-function@7.0.0(postcss@8.4.47):
+  postcss-image-set-function@7.0.0(postcss@8.4.49):
     dependencies:
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-import@15.1.0(postcss@8.4.47):
+  postcss-import@15.1.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
       read-cache: 1.0.0
       resolve: 1.22.8
 
-  postcss-import@16.1.0(postcss@8.4.47):
+  postcss-import@16.1.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
       read-cache: 1.0.0
       resolve: 1.22.8
 
-  postcss-js@4.0.1(postcss@8.4.47):
+  postcss-js@4.0.1(postcss@8.4.49):
     dependencies:
       camelcase-css: 2.0.1
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-lab-function@7.0.5(postcss@8.4.47):
+  postcss-lab-function@7.0.6(postcss@8.4.49):
     dependencies:
-      "@csstools/css-color-parser": 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/css-color-parser": 3.0.6(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/utilities": 2.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/utilities": 2.0.0(postcss@8.4.49)
+      postcss: 8.4.49
 
-  postcss-load-config@4.0.2(postcss@8.4.47):
+  postcss-load-config@4.0.2(postcss@8.4.49):
     dependencies:
-      lilconfig: 3.1.2
+      lilconfig: 3.1.3
       yaml: 2.6.0
     optionalDependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-logical@8.0.0(postcss@8.4.47):
+  postcss-logical@8.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-merge-longhand@7.0.4(postcss@8.4.47):
+  postcss-merge-longhand@7.0.4(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
-      stylehacks: 7.0.4(postcss@8.4.47)
+      stylehacks: 7.0.4(postcss@8.4.49)
 
-  postcss-merge-rules@7.0.4(postcss@8.4.47):
+  postcss-merge-rules@7.0.4(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
       caniuse-api: 3.0.0
-      cssnano-utils: 5.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      cssnano-utils: 5.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
-  postcss-minify-font-values@7.0.0(postcss@8.4.47):
+  postcss-minify-font-values@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-minify-gradients@7.0.0(postcss@8.4.47):
+  postcss-minify-gradients@7.0.0(postcss@8.4.49):
     dependencies:
       colord: 2.9.3
-      cssnano-utils: 5.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      cssnano-utils: 5.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-minify-params@7.0.2(postcss@8.4.47):
+  postcss-minify-params@7.0.2(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      cssnano-utils: 5.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      cssnano-utils: 5.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-minify-selectors@7.0.4(postcss@8.4.47):
+  postcss-minify-selectors@7.0.4(postcss@8.4.49):
     dependencies:
       cssesc: 3.0.0
-      postcss: 8.4.47
-      postcss-selector-parser: 6.1.2
-
-  postcss-nested@5.0.6(postcss@8.4.47):
-    dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
-  postcss-nested@6.2.0(postcss@8.4.47):
+  postcss-nested@6.2.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
-  postcss-nesting@13.0.1(postcss@8.4.47):
+  postcss-nesting@13.0.1(postcss@8.4.49):
     dependencies:
       "@csstools/selector-resolve-nested": 3.0.0(postcss-selector-parser@7.0.0)
       "@csstools/selector-specificity": 5.0.0(postcss-selector-parser@7.0.0)
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-normalize-charset@7.0.0(postcss@8.4.47):
+  postcss-normalize-charset@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-normalize-display-values@7.0.0(postcss@8.4.47):
+  postcss-normalize-display-values@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-positions@7.0.0(postcss@8.4.47):
+  postcss-normalize-positions@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-repeat-style@7.0.0(postcss@8.4.47):
+  postcss-normalize-repeat-style@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-string@7.0.0(postcss@8.4.47):
+  postcss-normalize-string@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-timing-functions@7.0.0(postcss@8.4.47):
+  postcss-normalize-timing-functions@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-unicode@7.0.2(postcss@8.4.47):
+  postcss-normalize-unicode@7.0.2(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-url@7.0.0(postcss@8.4.47):
+  postcss-normalize-url@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-whitespace@7.0.0(postcss@8.4.47):
+  postcss-normalize-whitespace@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-opacity-percentage@3.0.0(postcss@8.4.47):
+  postcss-opacity-percentage@3.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-ordered-values@7.0.1(postcss@8.4.47):
+  postcss-ordered-values@7.0.1(postcss@8.4.49):
     dependencies:
-      cssnano-utils: 5.0.0(postcss@8.4.47)
-      postcss: 8.4.47
+      cssnano-utils: 5.0.0(postcss@8.4.49)
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-overflow-shorthand@6.0.0(postcss@8.4.47):
+  postcss-overflow-shorthand@6.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-page-break@3.0.4(postcss@8.4.47):
+  postcss-page-break@3.0.4(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-place@10.0.0(postcss@8.4.47):
+  postcss-place@10.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-preset-env@10.0.9(postcss@8.4.47):
-    dependencies:
-      "@csstools/postcss-cascade-layers": 5.0.1(postcss@8.4.47)
-      "@csstools/postcss-color-function": 4.0.5(postcss@8.4.47)
-      "@csstools/postcss-color-mix-function": 3.0.5(postcss@8.4.47)
-      "@csstools/postcss-content-alt-text": 2.0.4(postcss@8.4.47)
-      "@csstools/postcss-exponential-functions": 2.0.4(postcss@8.4.47)
-      "@csstools/postcss-font-format-keywords": 4.0.0(postcss@8.4.47)
-      "@csstools/postcss-gamut-mapping": 2.0.5(postcss@8.4.47)
-      "@csstools/postcss-gradients-interpolation-method": 5.0.5(postcss@8.4.47)
-      "@csstools/postcss-hwb-function": 4.0.5(postcss@8.4.47)
-      "@csstools/postcss-ic-unit": 4.0.0(postcss@8.4.47)
-      "@csstools/postcss-initial": 2.0.0(postcss@8.4.47)
-      "@csstools/postcss-is-pseudo-class": 5.0.1(postcss@8.4.47)
-      "@csstools/postcss-light-dark-function": 2.0.7(postcss@8.4.47)
-      "@csstools/postcss-logical-float-and-clear": 3.0.0(postcss@8.4.47)
-      "@csstools/postcss-logical-overflow": 2.0.0(postcss@8.4.47)
-      "@csstools/postcss-logical-overscroll-behavior": 2.0.0(postcss@8.4.47)
-      "@csstools/postcss-logical-resize": 3.0.0(postcss@8.4.47)
-      "@csstools/postcss-logical-viewport-units": 3.0.3(postcss@8.4.47)
-      "@csstools/postcss-media-minmax": 2.0.4(postcss@8.4.47)
-      "@csstools/postcss-media-queries-aspect-ratio-number-values": 3.0.4(postcss@8.4.47)
-      "@csstools/postcss-nested-calc": 4.0.0(postcss@8.4.47)
-      "@csstools/postcss-normalize-display-values": 4.0.0(postcss@8.4.47)
-      "@csstools/postcss-oklab-function": 4.0.5(postcss@8.4.47)
-      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.47)
-      "@csstools/postcss-relative-color-syntax": 3.0.5(postcss@8.4.47)
-      "@csstools/postcss-scope-pseudo-class": 4.0.1(postcss@8.4.47)
-      "@csstools/postcss-stepped-value-functions": 4.0.4(postcss@8.4.47)
-      "@csstools/postcss-text-decoration-shorthand": 4.0.1(postcss@8.4.47)
-      "@csstools/postcss-trigonometric-functions": 4.0.4(postcss@8.4.47)
-      "@csstools/postcss-unset-value": 4.0.0(postcss@8.4.47)
-      autoprefixer: 10.4.20(postcss@8.4.47)
+  postcss-preset-env@10.1.2(postcss@8.4.49):
+    dependencies:
+      "@csstools/postcss-cascade-layers": 5.0.1(postcss@8.4.49)
+      "@csstools/postcss-color-function": 4.0.6(postcss@8.4.49)
+      "@csstools/postcss-color-mix-function": 3.0.6(postcss@8.4.49)
+      "@csstools/postcss-content-alt-text": 2.0.4(postcss@8.4.49)
+      "@csstools/postcss-exponential-functions": 2.0.5(postcss@8.4.49)
+      "@csstools/postcss-font-format-keywords": 4.0.0(postcss@8.4.49)
+      "@csstools/postcss-gamut-mapping": 2.0.6(postcss@8.4.49)
+      "@csstools/postcss-gradients-interpolation-method": 5.0.6(postcss@8.4.49)
+      "@csstools/postcss-hwb-function": 4.0.6(postcss@8.4.49)
+      "@csstools/postcss-ic-unit": 4.0.0(postcss@8.4.49)
+      "@csstools/postcss-initial": 2.0.0(postcss@8.4.49)
+      "@csstools/postcss-is-pseudo-class": 5.0.1(postcss@8.4.49)
+      "@csstools/postcss-light-dark-function": 2.0.7(postcss@8.4.49)
+      "@csstools/postcss-logical-float-and-clear": 3.0.0(postcss@8.4.49)
+      "@csstools/postcss-logical-overflow": 2.0.0(postcss@8.4.49)
+      "@csstools/postcss-logical-overscroll-behavior": 2.0.0(postcss@8.4.49)
+      "@csstools/postcss-logical-resize": 3.0.0(postcss@8.4.49)
+      "@csstools/postcss-logical-viewport-units": 3.0.3(postcss@8.4.49)
+      "@csstools/postcss-media-minmax": 2.0.5(postcss@8.4.49)
+      "@csstools/postcss-media-queries-aspect-ratio-number-values": 3.0.4(postcss@8.4.49)
+      "@csstools/postcss-nested-calc": 4.0.0(postcss@8.4.49)
+      "@csstools/postcss-normalize-display-values": 4.0.0(postcss@8.4.49)
+      "@csstools/postcss-oklab-function": 4.0.6(postcss@8.4.49)
+      "@csstools/postcss-progressive-custom-properties": 4.0.0(postcss@8.4.49)
+      "@csstools/postcss-random-function": 1.0.1(postcss@8.4.49)
+      "@csstools/postcss-relative-color-syntax": 3.0.6(postcss@8.4.49)
+      "@csstools/postcss-scope-pseudo-class": 4.0.1(postcss@8.4.49)
+      "@csstools/postcss-sign-functions": 1.1.0(postcss@8.4.49)
+      "@csstools/postcss-stepped-value-functions": 4.0.5(postcss@8.4.49)
+      "@csstools/postcss-text-decoration-shorthand": 4.0.1(postcss@8.4.49)
+      "@csstools/postcss-trigonometric-functions": 4.0.5(postcss@8.4.49)
+      "@csstools/postcss-unset-value": 4.0.0(postcss@8.4.49)
+      autoprefixer: 10.4.20(postcss@8.4.49)
       browserslist: 4.24.2
-      css-blank-pseudo: 7.0.1(postcss@8.4.47)
-      css-has-pseudo: 7.0.1(postcss@8.4.47)
-      css-prefers-color-scheme: 10.0.0(postcss@8.4.47)
-      cssdb: 8.1.2
-      postcss: 8.4.47
-      postcss-attribute-case-insensitive: 7.0.1(postcss@8.4.47)
-      postcss-clamp: 4.1.0(postcss@8.4.47)
-      postcss-color-functional-notation: 7.0.5(postcss@8.4.47)
-      postcss-color-hex-alpha: 10.0.0(postcss@8.4.47)
-      postcss-color-rebeccapurple: 10.0.0(postcss@8.4.47)
-      postcss-custom-media: 11.0.5(postcss@8.4.47)
-      postcss-custom-properties: 14.0.4(postcss@8.4.47)
-      postcss-custom-selectors: 8.0.4(postcss@8.4.47)
-      postcss-dir-pseudo-class: 9.0.1(postcss@8.4.47)
-      postcss-double-position-gradients: 6.0.0(postcss@8.4.47)
-      postcss-focus-visible: 10.0.1(postcss@8.4.47)
-      postcss-focus-within: 9.0.1(postcss@8.4.47)
-      postcss-font-variant: 5.0.0(postcss@8.4.47)
-      postcss-gap-properties: 6.0.0(postcss@8.4.47)
-      postcss-image-set-function: 7.0.0(postcss@8.4.47)
-      postcss-lab-function: 7.0.5(postcss@8.4.47)
-      postcss-logical: 8.0.0(postcss@8.4.47)
-      postcss-nesting: 13.0.1(postcss@8.4.47)
-      postcss-opacity-percentage: 3.0.0(postcss@8.4.47)
-      postcss-overflow-shorthand: 6.0.0(postcss@8.4.47)
-      postcss-page-break: 3.0.4(postcss@8.4.47)
-      postcss-place: 10.0.0(postcss@8.4.47)
-      postcss-pseudo-class-any-link: 10.0.1(postcss@8.4.47)
-      postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.47)
-      postcss-selector-not: 8.0.1(postcss@8.4.47)
-
-  postcss-pseudo-class-any-link@10.0.1(postcss@8.4.47):
-    dependencies:
-      postcss: 8.4.47
+      css-blank-pseudo: 7.0.1(postcss@8.4.49)
+      css-has-pseudo: 7.0.2(postcss@8.4.49)
+      css-prefers-color-scheme: 10.0.0(postcss@8.4.49)
+      cssdb: 8.2.3
+      postcss: 8.4.49
+      postcss-attribute-case-insensitive: 7.0.1(postcss@8.4.49)
+      postcss-clamp: 4.1.0(postcss@8.4.49)
+      postcss-color-functional-notation: 7.0.6(postcss@8.4.49)
+      postcss-color-hex-alpha: 10.0.0(postcss@8.4.49)
+      postcss-color-rebeccapurple: 10.0.0(postcss@8.4.49)
+      postcss-custom-media: 11.0.5(postcss@8.4.49)
+      postcss-custom-properties: 14.0.4(postcss@8.4.49)
+      postcss-custom-selectors: 8.0.4(postcss@8.4.49)
+      postcss-dir-pseudo-class: 9.0.1(postcss@8.4.49)
+      postcss-double-position-gradients: 6.0.0(postcss@8.4.49)
+      postcss-focus-visible: 10.0.1(postcss@8.4.49)
+      postcss-focus-within: 9.0.1(postcss@8.4.49)
+      postcss-font-variant: 5.0.0(postcss@8.4.49)
+      postcss-gap-properties: 6.0.0(postcss@8.4.49)
+      postcss-image-set-function: 7.0.0(postcss@8.4.49)
+      postcss-lab-function: 7.0.6(postcss@8.4.49)
+      postcss-logical: 8.0.0(postcss@8.4.49)
+      postcss-nesting: 13.0.1(postcss@8.4.49)
+      postcss-opacity-percentage: 3.0.0(postcss@8.4.49)
+      postcss-overflow-shorthand: 6.0.0(postcss@8.4.49)
+      postcss-page-break: 3.0.4(postcss@8.4.49)
+      postcss-place: 10.0.0(postcss@8.4.49)
+      postcss-pseudo-class-any-link: 10.0.1(postcss@8.4.49)
+      postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.49)
+      postcss-selector-not: 8.0.1(postcss@8.4.49)
+
+  postcss-pseudo-class-any-link@10.0.1(postcss@8.4.49):
+    dependencies:
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
-  postcss-reduce-initial@7.0.2(postcss@8.4.47):
+  postcss-reduce-initial@7.0.2(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
       caniuse-api: 3.0.0
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-reduce-transforms@7.0.0(postcss@8.4.47):
+  postcss-reduce-transforms@7.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  postcss-replace-overflow-wrap@4.0.0(postcss@8.4.47):
+  postcss-replace-overflow-wrap@4.0.0(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-reporter@7.1.0(postcss@8.4.47):
+  postcss-reporter@7.1.0(postcss@8.4.49):
     dependencies:
       picocolors: 1.1.1
-      postcss: 8.4.47
+      postcss: 8.4.49
       thenby: 1.3.4
 
   postcss-resolve-nested-selector@0.1.6: {}
 
-  postcss-safe-parser@7.0.1(postcss@8.4.47):
+  postcss-safe-parser@7.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
 
-  postcss-selector-not@8.0.1(postcss@8.4.47):
+  postcss-selector-not@8.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 7.0.0
 
   postcss-selector-parser@6.0.10:
@@ -13501,20 +13669,20 @@ snapshots:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  postcss-svgo@7.0.1(postcss@8.4.47):
+  postcss-svgo@7.0.1(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-value-parser: 4.2.0
       svgo: 3.3.2
 
-  postcss-unique-selectors@7.0.3(postcss@8.4.47):
+  postcss-unique-selectors@7.0.3(postcss@8.4.49):
     dependencies:
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
   postcss-value-parser@4.2.0: {}
 
-  postcss@8.4.47:
+  postcss@8.4.49:
     dependencies:
       nanoid: 3.3.7
       picocolors: 1.1.1
@@ -13526,15 +13694,15 @@ snapshots:
     dependencies:
       fast-diff: 1.3.0
 
-  prettier-plugin-organize-imports@4.1.0(prettier@3.3.3)(typescript@5.5.4):
+  prettier-plugin-organize-imports@4.1.0(prettier@3.4.2)(typescript@5.7.2):
     dependencies:
-      prettier: 3.3.3
-      typescript: 5.5.4
+      prettier: 3.4.2
+      typescript: 5.7.2
 
   prettier@2.8.8:
     optional: true
 
-  prettier@3.3.3: {}
+  prettier@3.4.2: {}
 
   pretty-bytes@5.6.0: {}
 
@@ -13755,15 +13923,15 @@ snapshots:
 
   sax@1.4.1: {}
 
-  semantic-release@24.2.0(typescript@5.5.4):
+  semantic-release@24.2.0(typescript@5.7.2):
     dependencies:
-      "@semantic-release/commit-analyzer": 13.0.0(semantic-release@24.2.0(typescript@5.5.4))
+      "@semantic-release/commit-analyzer": 13.0.0(semantic-release@24.2.0(typescript@5.7.2))
       "@semantic-release/error": 4.0.0
-      "@semantic-release/github": 11.0.0(semantic-release@24.2.0(typescript@5.5.4))
-      "@semantic-release/npm": 12.0.1(semantic-release@24.2.0(typescript@5.5.4))
-      "@semantic-release/release-notes-generator": 14.0.1(semantic-release@24.2.0(typescript@5.5.4))
+      "@semantic-release/github": 11.0.0(semantic-release@24.2.0(typescript@5.7.2))
+      "@semantic-release/npm": 12.0.1(semantic-release@24.2.0(typescript@5.7.2))
+      "@semantic-release/release-notes-generator": 14.0.1(semantic-release@24.2.0(typescript@5.7.2))
       aggregate-error: 5.0.0
-      cosmiconfig: 9.0.0(typescript@5.5.4)
+      cosmiconfig: 9.0.0(typescript@5.7.2)
       debug: 4.3.7
       env-ci: 11.1.0
       execa: 9.5.1
@@ -14011,31 +14179,31 @@ snapshots:
 
   style-mod@4.1.2: {}
 
-  stylehacks@7.0.4(postcss@8.4.47):
+  stylehacks@7.0.4(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-selector-parser: 6.1.2
 
-  stylelint-config-recommended@14.0.1(stylelint@16.10.0(typescript@5.5.4)):
+  stylelint-config-recommended@14.0.1(stylelint@16.12.0(typescript@5.7.2)):
     dependencies:
-      stylelint: 16.10.0(typescript@5.5.4)
+      stylelint: 16.12.0(typescript@5.7.2)
 
-  stylelint-config-standard@36.0.1(stylelint@16.10.0(typescript@5.5.4)):
+  stylelint-config-standard@36.0.1(stylelint@16.12.0(typescript@5.7.2)):
     dependencies:
-      stylelint: 16.10.0(typescript@5.5.4)
-      stylelint-config-recommended: 14.0.1(stylelint@16.10.0(typescript@5.5.4))
+      stylelint: 16.12.0(typescript@5.7.2)
+      stylelint-config-recommended: 14.0.1(stylelint@16.12.0(typescript@5.7.2))
 
-  stylelint@16.10.0(typescript@5.5.4):
+  stylelint@16.12.0(typescript@5.7.2):
     dependencies:
       "@csstools/css-parser-algorithms": 3.0.4(@csstools/css-tokenizer@3.0.3)
       "@csstools/css-tokenizer": 3.0.3
-      "@csstools/media-query-list-parser": 3.0.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
-      "@csstools/selector-specificity": 4.0.0(postcss-selector-parser@6.1.2)
+      "@csstools/media-query-list-parser": 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+      "@csstools/selector-specificity": 5.0.0(postcss-selector-parser@7.0.0)
       "@dual-bundle/import-meta-resolve": 4.1.0
       balanced-match: 2.0.0
       colord: 2.9.3
-      cosmiconfig: 9.0.0(typescript@5.5.4)
+      cosmiconfig: 9.0.0(typescript@5.7.2)
       css-functions-list: 3.2.3
       css-tree: 3.0.1
       debug: 4.3.7
@@ -14049,22 +14217,22 @@ snapshots:
       ignore: 6.0.2
       imurmurhash: 0.1.4
       is-plain-object: 5.0.0
-      known-css-properties: 0.34.0
+      known-css-properties: 0.35.0
       mathml-tag-names: 2.1.3
       meow: 13.2.0
       micromatch: 4.0.8
       normalize-path: 3.0.0
       picocolors: 1.1.1
-      postcss: 8.4.47
+      postcss: 8.4.49
       postcss-resolve-nested-selector: 0.1.6
-      postcss-safe-parser: 7.0.1(postcss@8.4.47)
-      postcss-selector-parser: 6.1.2
+      postcss-safe-parser: 7.0.1(postcss@8.4.49)
+      postcss-selector-parser: 7.0.0
       postcss-value-parser: 4.2.0
       resolve-from: 5.0.0
       string-width: 4.2.3
       supports-hyperlinks: 3.1.0
       svg-tags: 1.0.0
-      table: 6.8.2
+      table: 6.9.0
       write-file-atomic: 5.0.1
     transitivePeerDependencies:
       - supports-color
@@ -14117,7 +14285,7 @@ snapshots:
       "@pkgr/core": 0.1.1
       tslib: 2.8.1
 
-  table@6.8.2:
+  table@6.9.0:
     dependencies:
       ajv: 8.17.1
       lodash.truncate: 4.4.2
@@ -14125,7 +14293,7 @@ snapshots:
       string-width: 4.2.3
       strip-ansi: 6.0.1
 
-  tailwindcss@3.4.14:
+  tailwindcss@3.4.16:
     dependencies:
       "@alloc/quick-lru": 5.2.0
       arg: 5.0.2
@@ -14136,16 +14304,16 @@ snapshots:
       glob-parent: 6.0.2
       is-glob: 4.0.3
       jiti: 1.21.6
-      lilconfig: 2.1.0
+      lilconfig: 3.1.3
       micromatch: 4.0.8
       normalize-path: 3.0.0
       object-hash: 3.0.0
       picocolors: 1.1.1
-      postcss: 8.4.47
-      postcss-import: 15.1.0(postcss@8.4.47)
-      postcss-js: 4.0.1(postcss@8.4.47)
-      postcss-load-config: 4.0.2(postcss@8.4.47)
-      postcss-nested: 6.2.0(postcss@8.4.47)
+      postcss: 8.4.49
+      postcss-import: 15.1.0(postcss@8.4.49)
+      postcss-js: 4.0.1(postcss@8.4.49)
+      postcss-load-config: 4.0.2(postcss@8.4.49)
+      postcss-nested: 6.2.0(postcss@8.4.49)
       postcss-selector-parser: 6.1.2
       resolve: 1.22.8
       sucrase: 3.35.0
@@ -14179,8 +14347,6 @@ snapshots:
 
   text-extensions@2.4.0: {}
 
-  text-table@0.2.0: {}
-
   thenby@1.3.4: {}
 
   thenify-all@1.6.0:
@@ -14229,9 +14395,9 @@ snapshots:
 
   traverse@0.6.8: {}
 
-  ts-api-utils@1.4.0(typescript@5.5.4):
+  ts-api-utils@1.4.0(typescript@5.7.2):
     dependencies:
-      typescript: 5.5.4
+      typescript: 5.7.2
 
   ts-interface-checker@0.1.13: {}
 
@@ -14285,18 +14451,17 @@ snapshots:
       is-typed-array: 1.1.13
       possible-typed-array-names: 1.0.0
 
-  typescript-eslint@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4):
+  typescript-eslint@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2):
     dependencies:
-      "@typescript-eslint/eslint-plugin": 8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      "@typescript-eslint/parser": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-      "@typescript-eslint/utils": 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.5.4)
-    optionalDependencies:
-      typescript: 5.5.4
+      "@typescript-eslint/eslint-plugin": 8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      "@typescript-eslint/parser": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      "@typescript-eslint/utils": 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)
+      eslint: 9.17.0(jiti@2.4.1)
+      typescript: 5.7.2
     transitivePeerDependencies:
-      - eslint
       - supports-color
 
-  typescript@5.5.4: {}
+  typescript@5.7.2: {}
 
   uglify-js@3.19.3:
     optional: true
@@ -14372,30 +14537,32 @@ snapshots:
       spdx-correct: 3.2.0
       spdx-expression-parse: 3.0.1
 
-  vite-plugin-pwa@0.20.5(vite@5.4.10(@types/node@22.9.0)(terser@5.36.0))(workbox-build@7.3.0)(workbox-window@7.3.0):
+  vite-plugin-pwa@0.21.1(vite@6.0.3(@types/node@22.9.0)(jiti@2.4.1)(terser@5.36.0)(yaml@2.6.1))(workbox-build@7.3.0)(workbox-window@7.3.0):
     dependencies:
       debug: 4.3.7
       pretty-bytes: 6.1.1
       tinyglobby: 0.2.10
-      vite: 5.4.10(@types/node@22.9.0)(terser@5.36.0)
+      vite: 6.0.3(@types/node@22.9.0)(jiti@2.4.1)(terser@5.36.0)(yaml@2.6.1)
       workbox-build: 7.3.0
       workbox-window: 7.3.0
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.10(@types/node@22.9.0)(terser@5.36.0):
+  vite@6.0.3(@types/node@22.9.0)(jiti@2.4.1)(terser@5.36.0)(yaml@2.6.1):
     dependencies:
-      esbuild: 0.21.5
-      postcss: 8.4.47
+      esbuild: 0.24.0
+      postcss: 8.4.49
       rollup: 4.24.4
     optionalDependencies:
       "@types/node": 22.9.0
       fsevents: 2.3.3
+      jiti: 2.4.1
       terser: 5.36.0
+      yaml: 2.6.1
 
   w3c-keyname@2.2.8: {}
 
-  wavesurfer.js@7.8.8: {}
+  wavesurfer.js@7.8.11: {}
 
   wcwidth@1.0.1:
     dependencies:
@@ -14608,10 +14775,10 @@ snapshots:
 
   yallist@3.1.1: {}
 
-  yaml@2.5.1: {}
-
   yaml@2.6.0: {}
 
+  yaml@2.6.1: {}
+
   yargs-parser@18.1.3:
     dependencies:
       camelcase: 5.3.1
diff --git a/postcss.config.cjs b/postcss.config.cjs
index bf17a4351b138911b964ab9a412d1a5e20023d32..ec20059e3587700766b5a56bfed5b311540cabea 100644
--- a/postcss.config.cjs
+++ b/postcss.config.cjs
@@ -3,7 +3,6 @@
 module.exports = {
   plugins: [
     require("postcss-reporter"),
-    require("tailwindcss/nesting")(require("postcss-nesting")),
     require("tailwindcss"),
     require("postcss-preset-env")({
       stage: 4,
diff --git a/tests/database/ExampleDatabaseTest.php b/tests/database/ExampleDatabaseTest.php
index 8d4532667aaa92fcca7b867d51c1cec821e8051b..eda4a6ebbda58f96e510da41ffaaa363081076b6 100644
--- a/tests/database/ExampleDatabaseTest.php
+++ b/tests/database/ExampleDatabaseTest.php
@@ -45,6 +45,7 @@ class ExampleDatabaseTest extends CIUnitTestCase
         $model->delete($object->id);
 
         // The model should no longer find it
+        // @phpstan-ignore-next-line
         $this->assertNull($model->find($object->id));
 
         // ... but it should still be in the database
diff --git a/tests/modules/Api/Rest/V1/EpisodeTest.php b/tests/modules/Api/Rest/V1/EpisodeTest.php
index 2dba8fed4156bdab90c1c069888864f79baf4da7..5dfd9c52ec9dac878a9530122dbb32d20b464eea 100644
--- a/tests/modules/Api/Rest/V1/EpisodeTest.php
+++ b/tests/modules/Api/Rest/V1/EpisodeTest.php
@@ -8,6 +8,7 @@ use CodeIgniter\Database\Seeder;
 use CodeIgniter\Test\CIUnitTestCase;
 use CodeIgniter\Test\DatabaseTestTrait;
 use CodeIgniter\Test\FeatureTestTrait;
+use Override;
 use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
 
 class EpisodeTest extends CIUnitTestCase
@@ -45,11 +46,12 @@ class EpisodeTest extends CIUnitTestCase
      */
     private array $episode = [];
 
-    private readonly string $apiUrl;
+    private string $apiUrl = '';
 
-    public function __construct(?string $name = null)
+    #[Override]
+    protected function setUp(): void
     {
-        parent::__construct($name);
+        parent::setUp();
 
         $this->episode = FakeSinglePodcastApiSeeder::episode();
 
@@ -59,6 +61,15 @@ class EpisodeTest extends CIUnitTestCase
             ->gateway;
     }
 
+    #[Override]
+    protected function tearDown(): void
+    {
+        parent::tearDown();
+
+        restore_error_handler();
+        restore_exception_handler();
+    }
+
     public function testList(): void
     {
         $result = $this->call('get', $this->apiUrl . 'episodes');
diff --git a/tests/modules/Api/Rest/V1/PodcastTest.php b/tests/modules/Api/Rest/V1/PodcastTest.php
index b014913a34e6b283f757b767e57c388c6b319063..5c17bba8ab1261a370fc6ce66d00c6c1cd93a5d2 100644
--- a/tests/modules/Api/Rest/V1/PodcastTest.php
+++ b/tests/modules/Api/Rest/V1/PodcastTest.php
@@ -8,6 +8,7 @@ use CodeIgniter\Database\Seeder;
 use CodeIgniter\Test\CIUnitTestCase;
 use CodeIgniter\Test\DatabaseTestTrait;
 use CodeIgniter\Test\FeatureTestTrait;
+use Override;
 use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder;
 
 class PodcastTest extends CIUnitTestCase
@@ -45,11 +46,13 @@ class PodcastTest extends CIUnitTestCase
      */
     private array $podcast = [];
 
-    private readonly string $podcastApiUrl;
+    private string $podcastApiUrl = '';
 
-    public function __construct(?string $name = null)
+    #[Override]
+    protected function setUp(): void
     {
-        parent::__construct($name);
+        parent::setUp();
+
         $this->podcast = FakeSinglePodcastApiSeeder::podcast();
         $this->podcast['created_at'] = [];
         $this->podcast['updated_at'] = [];
@@ -57,6 +60,15 @@ class PodcastTest extends CIUnitTestCase
             ->gateway;
     }
 
+    #[Override]
+    protected function tearDown(): void
+    {
+        parent::tearDown();
+
+        restore_error_handler();
+        restore_exception_handler();
+    }
+
     public function testList(): void
     {
         $result = $this->call('get', $this->podcastApiUrl . 'podcasts');
diff --git a/tests/modules/Plugins/PluginsTest.php b/tests/modules/Plugins/PluginsTest.php
index b93c72d2157df64ae0af650dde50868196422cf4..a27e232c130c70e3e4aaf80671f39dc2541cc404 100644
--- a/tests/modules/Plugins/PluginsTest.php
+++ b/tests/modules/Plugins/PluginsTest.php
@@ -6,6 +6,7 @@ namespace Tests\Modules\Plugins;
 
 use App\Entities\Episode;
 use App\Entities\Podcast;
+use App\Libraries\HtmlHead;
 use App\Libraries\RssFeed;
 use CodeIgniter\Test\CIUnitTestCase;
 use CodeIgniter\Test\DatabaseTestTrait;
@@ -111,11 +112,21 @@ final class PluginsTest extends CIUnitTestCase
         self::$plugins->runHook('rssAfterItem', [$episode, $item]);
         $this->assertFalse(empty($item->efoo));
 
-        ob_start();
-        self::$plugins->runHook('siteHead', []);
-        $result = ob_get_contents();
-        ob_end_clean(); //Discard output buffer
-        $this->assertEquals('hello', $result);
+        $head = new HtmlHead();
+        self::$plugins->runHook('siteHead', [$head]);
+
+        $this->assertEquals(
+            (string) $head,
+            '<head>    <title >foo</title>    <meta charset="UTF-8"/>    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>    <link rel="icon" type="image/x-icon" href="http://localhost:8080/favicon.ico"/>    <link rel="apple-touch-icon" href="http://localhost:8080/icon-180.png"/>    <link rel="manifest" href="/manifest.webmanifest"/>    <meta name="theme-color" content="#009486"/>    <link rel="stylesheet" type="text/css" href="/themes/colors"/>    <title >foo</title>    <script>
+    // Check that service workers are supported
+    if (\'serviceWorker\' in navigator) {
+        // Use the window load event to keep the page load performant
+        window.addEventListener(\'load\', () => {
+            navigator.serviceWorker.register(\'/sw.js\');
+        });
+    }
+    </script></head>'
+        );
     }
 
     public function testRunHooksInactive(): void
diff --git a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php
index 249977f7a78297f2d73c99869a0d9cd243d90c68..53c93cdc97d72e47a9c11088ae93813f9cdafa2c 100644
--- a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php
+++ b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 use App\Entities\Episode;
 use App\Entities\Podcast;
+use App\Libraries\HtmlHead;
 use App\Libraries\RssFeed;
 use Modules\Plugins\Core\BasePlugin;
 
@@ -34,8 +35,8 @@ class AcmeAllHooksPlugin extends BasePlugin
     }
 
     #[Override]
-    public function siteHead(): void
+    public function siteHead(HtmlHead $head): void
     {
-        echo 'hello';
+        $head->tag('title', 'foo');
     }
 }
diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php
index c68b89891f175b5be9b4af61d8882280ee6693f7..84e08b611312129091434cf2ea66b8de23f90f20 100644
--- a/themes/cp_admin/_layout.php
+++ b/themes/cp_admin/_layout.php
@@ -8,25 +8,12 @@ $isEpisodeArea = isset($podcast) && isset($episode);
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="robots" content="noindex">
-
-    <title><?= $this->renderSection('title') ?> | Castopod Admin</title>
-    <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('webmanifest') ?>">
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/admin.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/admin-audio-player.ts', 'js') ?>
-</head>
+<?=
+service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/admin.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/admin-audio-player.ts', 'js'))
+?>
 
 <body class="relative grid items-start min-h-screen bg-base grid-cols-admin grid-rows-admin">
     <?= $this->include('_partials/_nav_header') ?>
@@ -39,7 +26,7 @@ $isEpisodeArea = isset($podcast) && isset($episode);
                     <div class="flex flex-wrap items-center truncate gap-x-2">
                     <?php if (($isEpisodeArea && $episode->is_premium) || ($isPodcastArea && $podcast->is_premium)): ?>
                         <div class="inline-flex items-center">
-                            <?php // @icon('exchange-dollar-fill')?>
+                            <?php // @icon("exchange-dollar-fill")?>
                             <x-IconButton uri="<?= route_to('subscription-list', $podcast->id) ?>" glyph="exchange-dollar-fill" variant="secondary" size="large" class="p-0 mr-2 border-0"><?= ($isEpisodeArea && $episode->is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?></x-IconButton>
                             <x-Heading tagName="h1" size="large" class="max-w-sm truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
                         </div>
diff --git a/themes/cp_admin/_sidebar.php b/themes/cp_admin/_sidebar.php
index 3bce82a8c0743e29c59648d743c34d685d6eddee..76c80c5e0748cac5a2afd0fba43e9a0df416dbbe 100644
--- a/themes/cp_admin/_sidebar.php
+++ b/themes/cp_admin/_sidebar.php
@@ -7,11 +7,11 @@ use Modules\Auth\Models\UserModel;
 
 $navigation = [
     'dashboard' => [
-        'icon'  => 'dashboard-fill', // @icon('dashboard-fill')
+        'icon'  => 'dashboard-fill', // @icon("dashboard-fill")
         'items' => ['admin'],
     ],
     'podcasts' => [
-        'icon'              => 'mic-fill', // @icon('mic-fill')
+        'icon'              => 'mic-fill', // @icon("mic-fill")
         'items'             => ['podcast-list', 'podcast-create', 'all-podcast-imports', 'podcast-imports-add'],
         'items-permissions' => [
             'podcast-create'      => 'podcasts.create',
@@ -22,7 +22,7 @@ $navigation = [
         'count-route' => 'podcast-list',
     ],
     'plugins' => [
-        'icon'         => 'puzzle-fill', // @icon('puzzle-fill')
+        'icon'         => 'puzzle-fill', // @icon("puzzle-fill")
         'items'        => ['plugins-installed'],
         'items-labels' => [
             'plugins-installed' => lang('Navigation.plugins-installed') . ' (' . service('plugins')->getInstalledCount() . ')',
@@ -34,7 +34,7 @@ $navigation = [
         'count-route' => 'plugins-installed',
     ],
     'persons' => [
-        'icon'              => 'folder-user-fill', // @icon('folder-user-fill')
+        'icon'              => 'folder-user-fill', // @icon("folder-user-fill")
         'items'             => ['person-list', 'person-create'],
         'items-permissions' => [
             'person-list'   => 'persons.manage',
@@ -45,7 +45,7 @@ $navigation = [
         'count-route' => 'person-list',
     ],
     'fediverse' => [
-        'icon'              => 'rocket-2-fill', // @icon('rocket-2-fill')
+        'icon'              => 'rocket-2-fill', // @icon("rocket-2-fill")
         'items'             => ['fediverse-blocked-actors', 'fediverse-blocked-domains'],
         'items-permissions' => [
             'fediverse-blocked-actors'  => 'fediverse.manage-blocks',
@@ -53,7 +53,7 @@ $navigation = [
         ],
     ],
     'users' => [
-        'icon'              => 'group-fill', // @icon('group-fill')
+        'icon'              => 'group-fill', // @icon("group-fill")
         'items'             => ['user-list', 'user-create'],
         'items-permissions' => [
             'user-list'   => 'users.manage',
@@ -64,7 +64,7 @@ $navigation = [
         'count-route' => 'user-list',
     ],
     'pages' => [
-        'icon'              => 'pages-fill', // @icon('pages-fill')
+        'icon'              => 'pages-fill', // @icon("pages-fill")
         'items'             => ['page-list', 'page-create'],
         'items-permissions' => [
             'page-list'   => 'pages.manage',
@@ -75,7 +75,7 @@ $navigation = [
         'count-route' => 'page-list',
     ],
     'settings' => [
-        'icon'              => 'settings-3-fill', // @icon('settings-3-fill')
+        'icon'              => 'settings-3-fill', // @icon("settings-3-fill")
         'items'             => ['settings-general', 'settings-theme', 'admin-about'],
         'items-permissions' => [
             'settings-general' => 'admin.settings',
diff --git a/themes/cp_admin/contributor/create.php b/themes/cp_admin/contributor/create.php
index 60e5805df74ba8292987c89dff81c564b4abe779..123036f18e9b17486ae6f371ec316520f0615287 100644
--- a/themes/cp_admin/contributor/create.php
+++ b/themes/cp_admin/contributor/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Contributor.add_contributor', [esc($podcast->title)]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Contributor.add_contributor', [esc($podcast->title)]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/contributor/delete.php b/themes/cp_admin/contributor/delete.php
index bda1d3c98744fd88664bfa1c1d5f56d14736a6bf..5b9f3aef1a1da1021c8c3ff7efe2415dff938d97 100644
--- a/themes/cp_admin/contributor/delete.php
+++ b/themes/cp_admin/contributor/delete.php
@@ -1,11 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Contributor.delete_form.title', [
-    'contributor' => $contributor->username,
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Contributor.delete_form.title', [
     'contributor' => $contributor->username,
diff --git a/themes/cp_admin/contributor/edit.php b/themes/cp_admin/contributor/edit.php
index c6a490a06839ec00fa3d7ff2de4a5aa648b7533d..33eb9101750107c84c0b76ff630883fba4b64fcb 100644
--- a/themes/cp_admin/contributor/edit.php
+++ b/themes/cp_admin/contributor/edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Contributor.edit_role', [esc($contributor->username)]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Contributor.edit_role', [esc($contributor->username)]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/contributor/list.php b/themes/cp_admin/contributor/list.php
index 4cf876f12f59c81b5ed2877c7eb80c3323715096..d8546fa149a46d7e9cdb44120b865e83eb008d54 100644
--- a/themes/cp_admin/contributor/list.php
+++ b/themes/cp_admin/contributor/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Contributor.podcast_contributors') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Contributor.podcast_contributors') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('contributor-add', $podcast->id) ?>" variant="primary" iconLeft="add-fill"><?= lang('Contributor.add') ?></x-Button>
 <?= $this->endSection() ?>
 
@@ -39,8 +35,8 @@
         [
             'header' => lang('Common.actions'),
             'cell'   => function ($contributor, $podcast) {
-                // @icon('pencil-fill')
-                // @icon('delete-bin-fill')
+                // @icon("pencil-fill")
+                // @icon("delete-bin-fill")
                 return '<x-Button uri="' . route_to('contributor-edit', $podcast->id, $contributor->id) . '" variant="secondary" iconLeft="pencil-fill" size="small">' . lang('Contributor.edit') . '</x-Button>' .
                 '<x-Button uri="' . route_to('contributor-remove', $podcast->id, $contributor->id) . '" variant="danger" iconLeft="delete-bin-fill" size="small">' . lang('Contributor.remove') . '</x-Button>';
             },
diff --git a/themes/cp_admin/contributor/view.php b/themes/cp_admin/contributor/view.php
index bb29610928967bcf77befc819ed589bc6a6094c4..fe0fa793b7c2054bf4ff93885eab57628989495e 100644
--- a/themes/cp_admin/contributor/view.php
+++ b/themes/cp_admin/contributor/view.php
@@ -1,12 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Contributor.view', [
-    'username'     => esc($contributor->username),
-    'podcastTitle' => esc($podcast->title),
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Contributor.view', [
     'username'     => esc($contributor->username),
diff --git a/themes/cp_admin/dashboard.php b/themes/cp_admin/dashboard.php
index 8932bde69af8a0996bbcd0747c5659efa445697a..6a8a3bacd822cbecc396da7cceef4dfce8950b95 100644
--- a/themes/cp_admin/dashboard.php
+++ b/themes/cp_admin/dashboard.php
@@ -1,10 +1,5 @@
-<?= helper('components') ?>
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Dashboard.home') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Dashboard.home') ?>
 <?= $this->endSection() ?>
@@ -12,15 +7,15 @@
 <?= $this->section('content') ?>
 
 <div class="flex flex-col items-stretch gap-4 lg:flex-row">
-    <?php // @icon('mic-fill')?>
+    <?php // @icon("mic-fill")?>
     <x-DashboardCard href="<?= $onlyPodcastId === null ? route_to('podcast-list') : route_to('podcast-view', $onlyPodcastId) ?>" glyph="mic-fill" title="<?= lang('Dashboard.podcasts.title') ?>" subtitle="<?= $podcastsData['last_published_at'] ? esc(lang('Dashboard.podcasts.last_published', [
         'lastPublicationDate' => local_date($podcastsData['last_published_at']),
     ], null, false)) : lang('Dashboard.podcasts.not_found') ?>"><?= $podcastsData['number_of_podcasts'] ?></x-DashboardCard>
-    <?php // @icon('play-fill')?>
+    <?php // @icon("play-fill")?>
     <x-DashboardCard href="<?= $onlyPodcastId === null ? '' : route_to('episode-list', $onlyPodcastId) ?>" glyph="play-fill" title="<?= lang('Dashboard.episodes.title') ?>" subtitle="<?= $episodesData['last_published_at'] ? esc(lang('Dashboard.episodes.last_published', [
         'lastPublicationDate' => local_date($episodesData['last_published_at']),
     ], null, false)) : lang('Dashboard.episodes.not_found') ?>"><?= $episodesData['number_of_episodes'] ?></x-DashboardCard>
-    <?php // @icon('database-2-fill')?>
+    <?php // @icon("database-2-fill")?>
     <x-DashboardCard glyph="database-2-fill" title="<?= lang('Dashboard.storage.title') ?>" subtitle="<?= lang('Dashboard.storage.subtitle', [
         'totalUploaded' => $storageData['total_uploaded'],
         'totalStorage'  => $storageData['limit'],
diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php
index b9b8f7cc75227192b78246a9793e9da5455ba982..fd0ef42bae48ec68826ae5fd84dea700a3e33ada 100644
--- a/themes/cp_admin/episode/_sidebar.php
+++ b/themes/cp_admin/episode/_sidebar.php
@@ -2,7 +2,7 @@
 
 $episodeNavigation = [
     'dashboard' => [
-        'icon'              => 'dashboard-fill', // @icon('dashboard-fill')
+        'icon'              => 'dashboard-fill', // @icon("dashboard-fill")
         'items'             => ['episode-view', 'episode-edit', 'episode-persons-manage', 'embed-add'],
         'items-permissions' => [
             'episode-view'           => 'episodes.view',
@@ -12,13 +12,13 @@ $episodeNavigation = [
         ],
     ],
     'plugins' => [
-        'icon'              => 'puzzle-fill', // @icon('puzzle-fill')
+        'icon'              => 'puzzle-fill', // @icon("puzzle-fill")
         'items'             => [],
         'items-labels'      => [],
         'items-permissions' => [],
     ],
     'clips' => [
-        'icon'              => 'clapperboard-fill', // @icon('clapperboard-fill')
+        'icon'              => 'clapperboard-fill', // @icon("clapperboard-fill")
         'items'             => ['video-clips-list', 'video-clips-create', 'soundbites-list', 'soundbites-create'],
         'items-permissions' => [
             'video-clips-list'   => 'episodes.manage-clips',
diff --git a/themes/cp_admin/episode/create.php b/themes/cp_admin/episode/create.php
index a63d5a95f99e1bde036e100f7dcb97939b35d16b..ee169d2f2bd133f690811c44c572cafc52c4c3a0 100644
--- a/themes/cp_admin/episode/create.php
+++ b/themes/cp_admin/episode/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.create') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.create') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/delete.php b/themes/cp_admin/episode/delete.php
index d275bfae335365cd9e201294932e8b87f8f4ab9e..e272f5bee72f6c9ffaeed4ac8b220654e7ded95e 100644
--- a/themes/cp_admin/episode/delete.php
+++ b/themes/cp_admin/episode/delete.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.delete') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.delete') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php
index 701227bae07d9c1ba88a4fcf284b9921dd29fa4b..7d9e8801b2638bec3ed5f1f5d1c5bdf8fc954758 100644
--- a/themes/cp_admin/episode/edit.php
+++ b/themes/cp_admin/episode/edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.edit') ?>
 <?= $this->endSection() ?>
@@ -279,10 +275,10 @@
 </form>
 
 <?php if ($episode->published_at === null): ?>
-    <?php // @icon('delete-bin-fill')?>
+    <?php // @icon("delete-bin-fill")?>
     <x-Button class="mt-8" variant="danger" uri="<?= route_to('episode-delete', $podcast->id, $episode->id) ?>" iconLeft="delete-bin-fill"><?= lang('Episode.delete') ?></x-Button>    
 <?php else: ?>
-    <?php // @icon('forbid-fill')?>
+    <?php // @icon("forbid-fill")?>
     <x-Button class="mt-8" variant="disabled" iconLeft="forbid-fill" data-tooltip="right" title="<?= lang('Episode.messages.unpublishBeforeDeleteTip') ?>"><?= lang('Episode.delete') ?></x-Button>
 <?php endif ?>
 
diff --git a/themes/cp_admin/episode/embed.php b/themes/cp_admin/episode/embed.php
index 47c2d1e5da4dd9da748a01f6ec563410f49cf270..d64e65e4f613d43c8e68b24952c4917557fa6ef8 100644
--- a/themes/cp_admin/episode/embed.php
+++ b/themes/cp_admin/episode/embed.php
@@ -6,10 +6,6 @@ $embedHeight = config('Embed')->height;
 
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.embed.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.embed.title') ?>
 <?= $this->endSection() ?>
@@ -34,13 +30,13 @@ $embedHeight = config('Embed')->height;
 
 <div class="flex items-center mt-8 gap-x-2">
     <x-Forms.Textarea isReadonly="true" class="w-full max-w-xl" name="iframe" rows="2" value="<?= esc("<iframe width=\"100%\" height=\"{$embedHeight}\" frameborder=\"0\" scrolling=\"no\" style=\"width: 100%; height: {$embedHeight}px; overflow: hidden;\" src=\"{$episode->embed_url}\"></iframe>") ?>" />
-    <?php // @icon('file-copy-fill')?>
+    <?php // @icon("file-copy-fill")?>
     <x-IconButton glyph="file-copy-fill" data-type="clipboard-copy" data-clipboard-target="iframe"><?= lang('Episode.embed.clipboard_iframe') ?></x-IconButton>
 </div>
 
 <div class="flex items-center mt-4 gap-x-2">
     <x-Forms.Input isReadonly="true" class="w-full max-w-xl" name="url" value="<?= esc($episode->embed_url) ?>" />
-    <?php // @icon('file-copy-fill')?>
+    <?php // @icon("file-copy-fill")?>
     <x-IconButton glyph="file-copy-fill" data-type="clipboard-copy" data-clipboard-target="url"><?= lang('Episode.embed.clipboard_url') ?></x-IconButton>
 </div>
 
diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php
index 3358eeb8c9a5e4394c3364aecd35570669b1ca5d..5ede63dac74e55657cbf3c7a4e43326be301e077 100644
--- a/themes/cp_admin/episode/list.php
+++ b/themes/cp_admin/episode/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.all_podcast_episodes') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.all_podcast_episodes') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('episode-create', $podcast->id) ?>" variant="primary" iconLeft="add-fill"><?= lang('Episode.create') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/episode/persons.php b/themes/cp_admin/episode/persons.php
index 0be4b9fe09a059f912abb527ea87cdcca06a49eb..884bc88a9566cf112efa965566355bf87c702b0e 100644
--- a/themes/cp_admin/episode/persons.php
+++ b/themes/cp_admin/episode/persons.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Person.episode_form.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Person.episode_form.title') ?> (<?= count($episode->persons) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button variant="primary" uri="<?= route_to('person-create') ?>" iconLeft="add-fill"><?= lang('Person.create') ?></x-Button>
 <?= $this->endSection() ?>
 
@@ -86,7 +82,7 @@
         [
             'header' => lang('Common.actions'),
             'cell'   => function ($person): string {
-                // @icon('delete-bin-fill')
+                // @icon("delete-bin-fill")
                 return '<x-Button uri="' . route_to('episode-person-remove', $person->podcast_id, $person->episode_id, $person->id) . '" variant="danger" size="small" iconLeft="delete-bin-fill">' . lang('Person.episode_form.remove') . '</x-Button>';
             },
         ],
diff --git a/themes/cp_admin/episode/publish.php b/themes/cp_admin/episode/publish.php
index 8e853dffabafb4e3cd4a647e814cbde551dce071..525c4bdbaa8edc3d06714054ff46444952d7b225 100644
--- a/themes/cp_admin/episode/publish.php
+++ b/themes/cp_admin/episode/publish.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.publish') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.publish') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/publish_date_edit.php b/themes/cp_admin/episode/publish_date_edit.php
index c142d05888a8b0e51423b270318016cf2a73cbe7..281f9f32fffaae2cffd5468db4d5d9ba844c54c0 100644
--- a/themes/cp_admin/episode/publish_date_edit.php
+++ b/themes/cp_admin/episode/publish_date_edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.publish_date_edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.publish_date_edit') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/publish_edit.php b/themes/cp_admin/episode/publish_edit.php
index 12cc3efc7d10241c669da1aaee1b6698a87f59d9..0d9ccf49eeddc6f5a1e9de4c912d0b55a6e4d1a6 100644
--- a/themes/cp_admin/episode/publish_edit.php
+++ b/themes/cp_admin/episode/publish_edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.publish_edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.publish_edit') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/soundbites_list.php b/themes/cp_admin/episode/soundbites_list.php
index d64a8ef6a44c61b43963476188148eb5109af15b..bff278219f4a2a5bcc446618cd8e522a9768dc34 100644
--- a/themes/cp_admin/episode/soundbites_list.php
+++ b/themes/cp_admin/episode/soundbites_list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Soundbite.list.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Soundbite.list.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('soundbites-create', $podcast->id, $episode->id) ?>" variant="primary" iconLeft="add-fill"><?= lang('Soundbite.create') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/episode/soundbites_new.php b/themes/cp_admin/episode/soundbites_new.php
index bbcd952e695d64e608df7a6e4d735a7553498ab1..a31147771a9b02b6e5ee3264ec70253dea00e30f 100644
--- a/themes/cp_admin/episode/soundbites_new.php
+++ b/themes/cp_admin/episode/soundbites_new.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Soundbite.form.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Soundbite.form.title') ?>
 <?= $this->endSection() ?>
@@ -28,7 +24,7 @@
         <input slot="duration" type="number" name="duration" placeholder="<?= lang('VideoClip.form.duration') ?>" step="0.001" />
     </audio-clipper>
 
-    <?php // @icon('arrow-right-fill')?>
+    <?php // @icon("arrow-right-fill")?>
     <x-Button variant="primary" type="submit" class="self-end mt-4" iconRight="arrow-right-fill"><?= lang('Soundbite.form.submit') ?></x-Button>
 
 </form>
diff --git a/themes/cp_admin/episode/unpublish.php b/themes/cp_admin/episode/unpublish.php
index 26281a532661a4ee5a7d6b9f50f6410fde2d8a7d..d1a3f7d1a87cd04b24d7ba764da4bc49d2bde8df 100644
--- a/themes/cp_admin/episode/unpublish.php
+++ b/themes/cp_admin/episode/unpublish.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Episode.unpublish') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Episode.unpublish') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/video_clip.php b/themes/cp_admin/episode/video_clip.php
index cc536f20adbb86b66d761307f189d4b83ce8d13a..40dbebc65b6d6b7b35c0ec12aed9fa38e50b1695 100644
--- a/themes/cp_admin/episode/video_clip.php
+++ b/themes/cp_admin/episode/video_clip.php
@@ -1,11 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('VideoClip.title', [
-    'videoClipLabel' => esc($videoClip->title),
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('VideoClip.title', [
     'videoClipLabel' => esc($videoClip->title),
diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php
index 5a70ac4dd360ba44a5d86c97c69d471265356252..f837630c5e50fc0a63cac643c33adaa855931ec2 100644
--- a/themes/cp_admin/episode/video_clips_list.php
+++ b/themes/cp_admin/episode/video_clips_list.php
@@ -6,16 +6,12 @@ use CodeIgniter\I18n\Time;
 ?>
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('VideoClip.list.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('VideoClip.list.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('video-clips-create', $podcast->id, $episode->id) ?>" variant="primary" iconLeft="add-fill"><?= lang('VideoClip.create') ?></x-Button>
 <?= $this->endSection() ?>
 
@@ -35,12 +31,12 @@ use CodeIgniter\I18n\Time;
                 ];
 
                 $pillIconMap = [
-                    'queued'   => 'timer-fill', // @icon('timer-fill')
-                    'pending'  => 'pause-fill', // @icon('pause-fill')
-                    'running'  => 'loader-fill', // @icon('loader-fill')
-                    'canceled' => 'forbid-fill', // @icon('forbid-fill')
-                    'failed'   => 'close-fill', // @icon('close-fill')
-                    'passed'   => 'check-fill', // @icon('check-fill')
+                    'queued'   => 'timer-fill', // @icon("timer-fill")
+                    'pending'  => 'pause-fill', // @icon("pause-fill")
+                    'running'  => 'loader-fill', // @icon("loader-fill")
+                    'canceled' => 'forbid-fill', // @icon("forbid-fill")
+                    'failed'   => 'close-fill', // @icon("close-fill")
+                    'passed'   => 'check-fill', // @icon("check-fill")
                 ];
 
                 $pillIconClassMap = [
@@ -97,7 +93,7 @@ use CodeIgniter\I18n\Time;
                 if ($videoClip->media) {
                     helper('misc');
                     $filename = 'clip-' . slugify($videoClip->title) . "-{$videoClip->start_time}-{$videoClip->end_time}";
-                    // @icon('import-fill')
+                    // @icon("import-fill")
                     $downloadButton = '<x-IconButton glyph="import-fill" uri="' . $videoClip->media->file_url . '" download="' . $filename . '">' . lang('VideoClip.download_clip') . '</x-IconButton>';
                 }
 
diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php
index c8a7b32565fd2a33b674002d0f9d23a8cd06a600..24f0e1e960c190be4fd6b9e56baf5be2deaa7fdb 100644
--- a/themes/cp_admin/episode/video_clips_new.php
+++ b/themes/cp_admin/episode/video_clips_new.php
@@ -1,13 +1,5 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('VideoClip.form.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('VideoClip.form.title') ?>
 <?= $this->endSection() ?>
@@ -71,7 +63,7 @@
             </div>
         </fieldset>
     </x-Forms.Section>
-    <?php // @icon('arrow-right-fill')?>
+    <?php // @icon("arrow-right-fill")?>
     <x-Button variant="primary" type="submit" iconRight="arrow-right-fill" class="self-end"><?= lang('VideoClip.form.submit') ?></x-Button>
 </div>
 </form>
diff --git a/themes/cp_admin/episode/video_clips_requirements.php b/themes/cp_admin/episode/video_clips_requirements.php
index d7b7e3fbff0a81257866a0a7f7b99557fe27cb5e..d1b4143586e3c65b02ad627ba25cae5fe32f633c 100644
--- a/themes/cp_admin/episode/video_clips_requirements.php
+++ b/themes/cp_admin/episode/video_clips_requirements.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('VideoClip.form.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('VideoClip.form.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/view.php b/themes/cp_admin/episode/view.php
index bb2281a6fdf3e418477d3880d028ddaa510e59e2..d42e85140b481fde8f2a519f7ebcc22913a39a2a 100644
--- a/themes/cp_admin/episode/view.php
+++ b/themes/cp_admin/episode/view.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($episode->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($episode->title) ?>    
 <?= $this->endSection() ?>
@@ -18,7 +14,7 @@
 
 <?= $this->section('headerRight') ?>
 <?php if ($episode->publication_status === 'published'): ?>
-<?php // @icon('history-fill')?>
+<?php // @icon("history-fill")?>
 <x-IconButton
     uri="<?= route_to('episode-publish_date_edit', $podcast->id, $episode->id) ?>"
     glyph="history-fill"
diff --git a/themes/cp_admin/fediverse/blocked_actors.php b/themes/cp_admin/fediverse/blocked_actors.php
index 9240a5d1f796f5fe0da14de73cc9696cf05a50d7..48c9d4ee4bc5a86e3587e6e02dfd45acb2a779fb 100644
--- a/themes/cp_admin/fediverse/blocked_actors.php
+++ b/themes/cp_admin/fediverse/blocked_actors.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Fediverse.blocked_actors') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Fediverse.blocked_actors') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/fediverse/blocked_domains.php b/themes/cp_admin/fediverse/blocked_domains.php
index 3d34a2339f432c8350034566b765cdded75c4232..8eb037cfcdf1506ff735c4928d490f9d3ebd43a3 100644
--- a/themes/cp_admin/fediverse/blocked_domains.php
+++ b/themes/cp_admin/fediverse/blocked_domains.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Fediverse.blocked_domains') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Fediverse.blocked_domains') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/import/_queue_table.php b/themes/cp_admin/import/_queue_table.php
index b607b9b166def600e9bb9d678241c6c5c937fb31..f187af3ebd4b50ce31cea6e2978ea7cbd2d1e4b8 100644
--- a/themes/cp_admin/import/_queue_table.php
+++ b/themes/cp_admin/import/_queue_table.php
@@ -21,12 +21,12 @@ use Modules\PodcastImport\Entities\TaskStatus;
                 ];
 
                 $pillIconMap = [
-                    'queued'   => 'timer-fill', // @icon('timer-fill')
-                    'pending'  => 'pause-fill', // @icon('pause-fill')
-                    'running'  => 'loader-fill', // @icon('loader-fill')
-                    'canceled' => 'forbid-fill', // @icon('forbid-fill')
-                    'failed'   => 'close-fill', // @icon('close-fill')
-                    'passed'   => 'check-fill', // @icon('check-fill')
+                    'queued'   => 'timer-fill', // @icon("timer-fill")
+                    'pending'  => 'pause-fill', // @icon("pause-fill")
+                    'running'  => 'loader-fill', // @icon("loader-fill")
+                    'canceled' => 'forbid-fill', // @icon("forbid-fill")
+                    'failed'   => 'close-fill', // @icon("close-fill")
+                    'passed'   => 'check-fill', // @icon("check-fill")
                 ];
 
                 $pillIconClassMap = [
diff --git a/themes/cp_admin/import/add_to_queue.php b/themes/cp_admin/import/add_to_queue.php
index 8dab08fd1ccc02d44ecc910af221560ca40a03f2..9a706bd7db9489ce9960e83c32501720880a4bf2 100644
--- a/themes/cp_admin/import/add_to_queue.php
+++ b/themes/cp_admin/import/add_to_queue.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.import') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.import') ?>
 <?= $this->endSection() ?>
@@ -15,7 +11,7 @@
 
 <x-Forms.Section
     title="<?= lang('PodcastImport.old_podcast_section_title') ?>">
-<?php // @icon('scales-3-fill')?>
+<?php // @icon("scales-3-fill")?>
 <x-Alert glyph="scales-3-fill" variant="info" title="<?= lang('PodcastImport.old_podcast_legal_disclaimer_title') ?>"><?= lang('PodcastImport.old_podcast_legal_disclaimer') ?></x-Alert>
 <x-Forms.Field
     name="imported_feed_url"
diff --git a/themes/cp_admin/import/podcast_queue.php b/themes/cp_admin/import/podcast_queue.php
index 0a3b7858781fb9e4bac914f6d260dd6cf8698943..a1aa7b6e45c04aece21137c72cd47bacfda07855 100644
--- a/themes/cp_admin/import/podcast_queue.php
+++ b/themes/cp_admin/import/podcast_queue.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.all_imports') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.all_imports') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('loop-left-fill')?>
+<?php // @icon("loop-left-fill")?>
 <x-Button uri="<?= route_to('podcast-imports-sync', $podcast->id) ?>" variant="primary" iconLeft="loop-left-fill"><?= lang('PodcastImport.syncForm.title') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/import/podcast_sync.php b/themes/cp_admin/import/podcast_sync.php
index 702c0680775072ae8e02bd3ae7f910a190cae27c..5a57600e1111989a4109feeb96f00630cd79c37d 100644
--- a/themes/cp_admin/import/podcast_sync.php
+++ b/themes/cp_admin/import/podcast_sync.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('PodcastImport.syncForm.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('PodcastImport.syncForm.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/import/queue.php b/themes/cp_admin/import/queue.php
index 8253a9ab7c566e74f768e9c15fa18c9f62f59564..06c9696896e4d5bd07836fc268c179d94d87f116 100644
--- a/themes/cp_admin/import/queue.php
+++ b/themes/cp_admin/import/queue.php
@@ -1,18 +1,11 @@
-<?php declare(strict_types=1);
-
-?>
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.all_imports') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.all_imports') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('podcast-imports-add') ?>" variant="primary" iconLeft="add-fill"><?= lang('Podcast.import') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/my_account/change_password.php b/themes/cp_admin/my_account/change_password.php
index 93df4baff12c9b9baa7024eac5ab89f3e6c93f85..7c41193edcac0cbaf752b305dcc400c3a86b610b 100644
--- a/themes/cp_admin/my_account/change_password.php
+++ b/themes/cp_admin/my_account/change_password.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('MyAccount.changePassword') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('MyAccount.changePassword') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/my_account/view.php b/themes/cp_admin/my_account/view.php
index 5d5c11019d2268f8c5ff0d16e70841fa6b0b9985..46ebb1c88294b414079b091213281f25116bf58e 100644
--- a/themes/cp_admin/my_account/view.php
+++ b/themes/cp_admin/my_account/view.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('MyAccount.info') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('MyAccount.info') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/page/create.php b/themes/cp_admin/page/create.php
index 2ab5c74cf2dea1f8c35bf9376d58d5b92cb901c3..33cc2d004f888de078d54adefbd1894bf8086131 100644
--- a/themes/cp_admin/page/create.php
+++ b/themes/cp_admin/page/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Page.create') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Page.create') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/page/edit.php b/themes/cp_admin/page/edit.php
index 614d50c407f74abfcfde8403a45bce0c95cc921e..e128425e3e16def60dd7043b7a328c9e28358cf4 100644
--- a/themes/cp_admin/page/edit.php
+++ b/themes/cp_admin/page/edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Page.edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Page.edit') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/page/list.php b/themes/cp_admin/page/list.php
index e64deb01e66bf8b0d9e4c00c7c9fc367e0aec33b..b03b5e20360252f8c01824747ba3a722d6727119 100644
--- a/themes/cp_admin/page/list.php
+++ b/themes/cp_admin/page/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Page.all_pages') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Page.all_pages') ?> (<?= count($pages) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('page-create') ?>" variant="primary" iconLeft="add-fill"><?= lang('Page.create') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/page/view.php b/themes/cp_admin/page/view.php
index 8d26eb31d5526c24093cc7707d3a0f21c51f69e9..74f001f1e7ffd45f404a3a215eda5fa5c8400dde 100644
--- a/themes/cp_admin/page/view.php
+++ b/themes/cp_admin/page/view.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($page->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($page->title) ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button variant="primary" uri="<?= route_to('page-edit', $page->id) ?>" iconLeft="add-fill"><?= lang('Page.edit') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/person/create.php b/themes/cp_admin/person/create.php
index be249baac1ba5abc4658f4191bd3645217b9485a..d671f63fcf44ceec97edc0d25e7bc37a4bc3ffca 100644
--- a/themes/cp_admin/person/create.php
+++ b/themes/cp_admin/person/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Person.create') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Person.create') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/person/edit.php b/themes/cp_admin/person/edit.php
index 816daa997b8b9fe94a7d37c464b6a8decbfd3b93..310f0f3897c0d9dc63b1a7f2ed78621ae17f5d99 100644
--- a/themes/cp_admin/person/edit.php
+++ b/themes/cp_admin/person/edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Person.edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Person.edit') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/person/list.php b/themes/cp_admin/person/list.php
index ba70ecd94f8d2e453c7b30b84583735ba22d382f..01775d25e664d0f5f45fbd6235e24f13662388c2 100644
--- a/themes/cp_admin/person/list.php
+++ b/themes/cp_admin/person/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Person.all_persons') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Person.all_persons') ?> (<?= count($persons) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('person-create') ?>" variant="primary" iconLeft="add-fill"><?= lang('Person.create') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/person/view.php b/themes/cp_admin/person/view.php
index 9be4c4ed6318b2845ce658fb38c0a743520bb57d..b4ccc0d5a10abb10850377f4a7ccd6cb1f04d37a 100644
--- a/themes/cp_admin/person/view.php
+++ b/themes/cp_admin/person/view.php
@@ -1,16 +1,12 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($person->full_name) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($person->full_name) ?>
 
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('pencil-fill')?>
+<?php // @icon("pencil-fill")?>
 <x-Button uri="<?= route_to('person-edit', $person->id) ?>" variant="secondary" iconLeft="pencil-fill"><?= lang('Person.edit') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/plugins/_field.php b/themes/cp_admin/plugins/_field.php
index f236e58c97417f51f9cc3a945202e207fa3c1dd4..82ac3036ed582a4afcc9a49bb726a68fe1101672 100644
--- a/themes/cp_admin/plugins/_field.php
+++ b/themes/cp_admin/plugins/_field.php
@@ -106,6 +106,19 @@ case 'textarea': ?>
         value="<?= $value ?>"
     />
 <?php break;
+case 'html': ?>
+    <x-Forms.Field
+        as="CodeEditor"
+        lang="html"
+        class="<?= $class ?>"
+        name="<?= $name ?>"
+        label="<?= $label ?>"
+        hint="<?= $hint ?>"
+        helper="<?= $helper ?>"
+        isRequired="<?= $optional ? 'false' : 'true' ?>"
+        content="<?= htmlspecialchars($value) ?>"
+    />
+<?php break;
 case 'markdown': ?>
     <x-Forms.Field
         as="MarkdownEditor"
@@ -120,7 +133,8 @@ case 'markdown': ?>
 <?php break;
 case 'rss': ?>
     <x-Forms.Field
-        as="XMLEditor"
+        as="CodeEditor"
+        lang="xml"
         class="<?= $class ?>"
         name="<?= $name ?>"
         label="<?= $label ?>"
diff --git a/themes/cp_admin/plugins/_plugin.php b/themes/cp_admin/plugins/_plugin.php
index 8be4724dd9828f4246149ebca392b16d7785b8ce..ed1dff479622f418f6f8dc35589e0551fd2c73c9 100644
--- a/themes/cp_admin/plugins/_plugin.php
+++ b/themes/cp_admin/plugins/_plugin.php
@@ -6,16 +6,16 @@ use Modules\Plugins\Core\PluginStatus;
 <article class="flex flex-col p-4 rounded-xl relative bg-elevated border-3 <?= $plugin->getStatus() === PluginStatus::ACTIVE ? 'border-accent-base' : 'border-subtle' ?>">
     <div class="self-end -mb-6">
     <?php if($plugin->getStatus() === PluginStatus::ACTIVE): ?>
-        <?php // @icon('check-fill')?>
+        <?php // @icon("check-fill")?>
         <x-Pill variant="success" icon="check-fill" class="lowercase" size="small"><?= lang('Plugins.active') ?></x-Pill>
     <?php elseif($plugin->getStatus() === PluginStatus::INACTIVE): ?>
-        <?php // @icon('close-fill')?>
+        <?php // @icon("close-fill")?>
         <x-Pill variant="default" icon="close-fill" class="lowercase" size="small"><?= lang('Plugins.inactive') ?></x-Pill>
     <?php elseif($plugin->getStatus() === PluginStatus::INVALID): ?>
-        <?php // @icon('alert-fill')?>
+        <?php // @icon("alert-fill")?>
         <x-Pill variant="warning" icon="alert-fill" class="lowercase" size="small"><?= lang('Plugins.invalid') ?></x-Pill>
     <?php elseif($plugin->getStatus() === PluginStatus::INCOMPATIBLE): ?>
-        <?php // @icon('alert-fill')?>
+        <?php // @icon("alert-fill")?>
         <x-Pill variant="danger" icon="alert-fill" class="lowercase" size="small" hint="<?= lang('Plugins.incompatible_hint', [
             'minCastopodVersion' => $plugin->getMinCastopodVersion(),
         ]) ?>"><?= lang('Plugins.incompatible') ?></x-Pill>
@@ -36,11 +36,11 @@ use Modules\Plugins\Core\PluginStatus;
     <footer class="flex items-center justify-between mt-auto">
         <div class="flex gap-x-2">
             <?php if ($plugin->getHomepage()): ?>
-                <?php // @icon('earth-fill')?>
+                <?php // @icon("earth-fill")?>
                 <x-IconButton glyph="earth-fill" uri="<?= $plugin->getHomepage() ?>" isExternal="true"><?= lang('Plugins.website') ?></x-IconButton>    
             <?php endif; ?>
             <?php if ($plugin->getRepository()): ?>
-                <?php // @icon('git-repository-fill')?>
+                <?php // @icon("git-repository-fill")?>
                 <x-IconButton glyph="git-repository-fill" uri="<?= $plugin->getRepository()->url ?>" isExternal="true"><?= lang('Plugins.repository') ?></x-IconButton>    
             <?php endif; ?>
         </div>
@@ -57,7 +57,7 @@ use Modules\Plugins\Core\PluginStatus;
             </form>
         <?php endif; ?>
             <?php if ($plugin->getSettingsFields('general') !== []): ?>
-                <?php // @icon('equalizer-fill')?>
+                <?php // @icon("equalizer-fill")?>
                 <x-IconButton uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings') ?></x-IconButton>
             <?php endif; ?>
             <button class="p-2 rounded-full" id="more-dropdown-<?= $plugin->getKey() ?>" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $plugin->getKey() ?>-menu" aria-haspopup="true" aria-expanded="false" title="<?= lang('Common.more') ?>"><?= icon('more-2-fill') ?></button>
diff --git a/themes/cp_admin/plugins/installed.php b/themes/cp_admin/plugins/installed.php
index 77b3be79126254f9ff56f86724e0dd96a5160875..3e5735cd1f0beb9344f71b69e739dd4ad6297387 100644
--- a/themes/cp_admin/plugins/installed.php
+++ b/themes/cp_admin/plugins/installed.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Plugins.installed') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Plugins.installed') . ' (' . $total . ')' ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/plugins/settings.php b/themes/cp_admin/plugins/settings.php
index 1a0541677b16f03e7ea3bf4b67792e5de43e1316..c317c8b47ca818fbfefcd2c74a73d104b76d1aac 100644
--- a/themes/cp_admin/plugins/settings.php
+++ b/themes/cp_admin/plugins/settings.php
@@ -1,12 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Plugins.settingsTitle', [
-    'pluginTitle' => $plugin->getTitle(),
-    'type'        => $type,
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Plugins.settingsTitle', [
     'pluginTitle' => $plugin->getTitle(),
diff --git a/themes/cp_admin/plugins/view.php b/themes/cp_admin/plugins/view.php
index a0310ff16d0a66e463b4858935427ad52217d837..25f90adbd8f82e21352c0d1d503c82ad3eeca58f 100644
--- a/themes/cp_admin/plugins/view.php
+++ b/themes/cp_admin/plugins/view.php
@@ -4,23 +4,19 @@
 
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= $plugin->getTitle() ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= $plugin->getTitle() ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerLeft') ?>
 <?php if($plugin->getStatus() === PluginStatus::ACTIVE): ?>
-    <?php // @icon('check-fill')?>
+    <?php // @icon("check-fill")?>
     <x-Pill variant="success" icon="check-fill" class="lowercase"><?= lang('Plugins.active') ?></x-Pill>
 <?php elseif($plugin->getStatus() === PluginStatus::INACTIVE): ?>
-    <?php // @icon('close-fill')?>
+    <?php // @icon("close-fill")?>
     <x-Pill variant="default" icon="close-fill" class="lowercase"><?= lang('Plugins.inactive') ?></x-Pill>
 <?php elseif($plugin->getStatus() === PluginStatus::INVALID): ?>
-    <?php // @icon('alert-fill')?>
+    <?php // @icon("alert-fill")?>
     <x-Pill variant="warning" icon="alert-fill" class="lowercase"><?= lang('Plugins.invalid') ?></x-Pill>
 <?php endif; ?>
 <?= $this->endSection() ?>
@@ -31,7 +27,7 @@
         <?= csrf_field() ?>
         <x-Button type="submit" variant="danger"><?= lang('Plugins.deactivate') ?></x-Button>
         <?php if ($plugin->getSettingsFields('general') !== []): ?>
-        <?php // @icon('equalizer-fill')?>
+        <?php // @icon("equalizer-fill")?>
         <x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
         <?php endif; ?>
     </form>
@@ -40,7 +36,7 @@
         <?= csrf_field() ?>
         <x-Button type="submit" variant="secondary"><?= lang('Plugins.activate') ?></x-Button>
         <?php if ($plugin->getSettingsFields('general') !== []): ?>
-        <?php // @icon('equalizer-fill')?>
+        <?php // @icon("equalizer-fill")?>
         <x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
         <?php endif; ?>
     </form>
@@ -92,13 +88,13 @@
                     <li>
                         <?= $author->name ?>
                         <?php if ($author->email): ?>
-                            <?php // @icon('mail-fill')?>
+                            <?php // @icon("mail-fill")?>
                             <x-IconButton glyph="mail-fill" uri="mailto:<?= $author->email ?>" size="small" isExternal="true"><?= lang('Plugins.author_email', [
                                 'authorName' => $author->name,
                             ]) ?></x-IconButton>
                         <?php endif; ?>
                         <?php if ($author->url): ?>
-                            <?php // @icon('earth-fill')?>
+                            <?php // @icon("earth-fill")?>
                             <x-IconButton glyph="earth-fill" uri="<?= $author->url ?>" size="small" isExternal="true"><?= lang('Plugins.author_homepage', [
                                 'authorName' => $author->name,
                             ]) ?></x-IconButton>
diff --git a/themes/cp_admin/podcast/_sidebar.php b/themes/cp_admin/podcast/_sidebar.php
index dc09d9d84b72bbf0a5cc4216ad55932b7fc82500..a6967cb368f23eaa31bcb4fa2ae39d85015cfdb2 100644
--- a/themes/cp_admin/podcast/_sidebar.php
+++ b/themes/cp_admin/podcast/_sidebar.php
@@ -2,7 +2,7 @@
 
 $podcastNavigation = [
     'dashboard' => [
-        'icon'              => 'dashboard-fill', // @icon('dashboard-fill')
+        'icon'              => 'dashboard-fill', // @icon("dashboard-fill")
         'items'             => ['podcast-view', 'podcast-edit', 'podcast-persons-manage', 'podcast-imports', 'podcast-imports-sync'],
         'items-permissions' => [
             'podcast-view'           => 'view',
@@ -13,7 +13,7 @@ $podcastNavigation = [
         ],
     ],
     'episodes' => [
-        'icon'              => 'play-circle-fill', // @icon('play-circle-fill')
+        'icon'              => 'play-circle-fill', // @icon("play-circle-fill")
         'items'             => ['episode-list', 'episode-create'],
         'items-permissions' => [
             'episode-list'   => 'episodes.view',
@@ -24,13 +24,13 @@ $podcastNavigation = [
         'count-route' => 'episode-list',
     ],
     'plugins' => [
-        'icon'              => 'puzzle-fill', // @icon('puzzle-fill')
+        'icon'              => 'puzzle-fill', // @icon("puzzle-fill")
         'items'             => [],
         'items-labels'      => [],
         'items-permissions' => [],
     ],
     'analytics' => [
-        'icon'  => 'line-chart-fill', // @icon('line-chart-fill')
+        'icon'  => 'line-chart-fill', // @icon("line-chart-fill")
         'items' => [
             'podcast-analytics',
             'podcast-analytics-unique-listeners',
@@ -51,7 +51,7 @@ $podcastNavigation = [
         ],
     ],
     'broadcast' => [
-        'icon'  => 'broadcast-fill', // @icon('broadcast-fill')
+        'icon'  => 'broadcast-fill', // @icon("broadcast-fill")
         'items' => [
             'platforms-podcasting',
             'platforms-social',
@@ -62,7 +62,7 @@ $podcastNavigation = [
         ],
     ],
     'monetization' => [
-        'icon'  => 'money-dollar-circle-fill', // @icon('money-dollar-circle-fill')
+        'icon'  => 'money-dollar-circle-fill', // @icon("money-dollar-circle-fill")
         'items' => [
             'subscription-list',
             'subscription-create',
@@ -75,7 +75,7 @@ $podcastNavigation = [
         ],
     ],
     'contributors' => [
-        'icon'              => 'group-fill', // @icon('group-fill')
+        'icon'              => 'group-fill', // @icon("group-fill")
         'items'             => ['contributor-list', 'contributor-add'],
         'items-permissions' => [
             'contributor-list' => 'manage-contributors',
diff --git a/themes/cp_admin/podcast/analytics/index.php b/themes/cp_admin/podcast/analytics/index.php
index 0275ddea4181dcdabad903af5ff207802148d4cf..9407f8289de98c6a82e427f66eeff939b5a61fb2 100644
--- a/themes/cp_admin/podcast/analytics/index.php
+++ b/themes/cp_admin/podcast/analytics/index.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/listening_time.php b/themes/cp_admin/podcast/analytics/listening_time.php
index 0c575e6cb781d3fd5b97676da11ec4a171524e0a..e84615a390aabb60811d5b6900c8ce5502ae3162 100644
--- a/themes/cp_admin/podcast/analytics/listening_time.php
+++ b/themes/cp_admin/podcast/analytics/listening_time.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/locations.php b/themes/cp_admin/podcast/analytics/locations.php
index 184c112a354b6c3f4b2fb43ab0662f2a1652277d..da24a3dc3934da58b5165a38554d0db345d70dc4 100644
--- a/themes/cp_admin/podcast/analytics/locations.php
+++ b/themes/cp_admin/podcast/analytics/locations.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/players.php b/themes/cp_admin/podcast/analytics/players.php
index 50ca2c2df0b8a3d8b8803062168cfaea890faa9c..4740689c41c2559d7d62f00a50e95192d4356829 100644
--- a/themes/cp_admin/podcast/analytics/players.php
+++ b/themes/cp_admin/podcast/analytics/players.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/time_periods.php b/themes/cp_admin/podcast/analytics/time_periods.php
index f1dac21a705ff106d8fd6c9b0cb64ac02c106480..83e7a023f62585ee2757a7a1a39d1b6078993dc6 100644
--- a/themes/cp_admin/podcast/analytics/time_periods.php
+++ b/themes/cp_admin/podcast/analytics/time_periods.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/unique_listeners.php b/themes/cp_admin/podcast/analytics/unique_listeners.php
index 3fff9be4394ffcd2fff81711774c1e36111fb598..df2cdeec3d1052471816c667ad7ca668cb50b187 100644
--- a/themes/cp_admin/podcast/analytics/unique_listeners.php
+++ b/themes/cp_admin/podcast/analytics/unique_listeners.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/analytics/webpages.php b/themes/cp_admin/podcast/analytics/webpages.php
index 47e5b4b7f3b51d8a57ac30427e174b49c0dac892..238775959269df2bd20f4d50a187dce3195713b0 100644
--- a/themes/cp_admin/podcast/analytics/webpages.php
+++ b/themes/cp_admin/podcast/analytics/webpages.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/create.php b/themes/cp_admin/podcast/create.php
index 84b10c380c57e42955ecf7b96c68eba2d5bc7fb5..1d1ff82baa043d2fc9155c6ae7bbd88b6e789a28 100644
--- a/themes/cp_admin/podcast/create.php
+++ b/themes/cp_admin/podcast/create.php
@@ -1,13 +1,5 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.create') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.create') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/delete.php b/themes/cp_admin/podcast/delete.php
index d2f5217d954dd271956749b6b87ed9c8763e44d6..64520a6bd6ca8c60a648631a87364a2d71ddd34d 100644
--- a/themes/cp_admin/podcast/delete.php
+++ b/themes/cp_admin/podcast/delete.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.delete') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.delete') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php
index 65cc952dab49c75916102d5dbbb07221d51796e0..f2b3430e27a650e0a41c9c20ede295311eed4ecb 100644
--- a/themes/cp_admin/podcast/edit.php
+++ b/themes/cp_admin/podcast/edit.php
@@ -1,13 +1,5 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.edit') ?>
 <?= $this->endSection() ?>
@@ -234,7 +226,7 @@ value="<?= esc($podcast->new_feed_url) ?>"
 </div>
 
 </form>
-<?php // @icon('delete-bin-fill')?>
+<?php // @icon("delete-bin-fill")?>
 <x-Button class="mt-8" variant="danger" uri="<?= route_to('podcast-delete', $podcast->id) ?>" iconLeft="delete-bin-fill"><?= lang('Podcast.delete') ?></x-Button>
 
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/list.php b/themes/cp_admin/podcast/list.php
index 0875879794957d77b5ce5812ba59921847a730ba..5fc2a6cbd73f62b09e9a32dc96076bcea3510937 100644
--- a/themes/cp_admin/podcast/list.php
+++ b/themes/cp_admin/podcast/list.php
@@ -1,17 +1,13 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.all_podcasts') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.all_podcasts') ?> (<?= count($podcasts) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
 <?php
-// @icon('import-fill')
-// @icon('add-fill')
+// @icon("import-fill")
+// @icon("add-fill")
 ?>
 <x-Button uri="<?= route_to('podcast-imports-add') ?>" variant="secondary" iconLeft="import-fill"><?= lang('Podcast.import') ?></x-Button>
 <x-Button uri="<?= route_to('podcast-create') ?>" variant="primary" iconLeft="add-fill"><?= lang('Podcast.create') ?></x-Button>
diff --git a/themes/cp_admin/podcast/notifications.php b/themes/cp_admin/podcast/notifications.php
index 8551ced5fb524766cded88e0284558edac08920b..336b2680f6bcf890044fa868769491fba8e8d688 100644
--- a/themes/cp_admin/podcast/notifications.php
+++ b/themes/cp_admin/podcast/notifications.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Notifications.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Notifications.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/persons.php b/themes/cp_admin/podcast/persons.php
index f6e4e1aab899a0d55a34e9c3f5704b3f2599bf4c..8e02dc1dde931064718741b63b2880941fd40cdc 100644
--- a/themes/cp_admin/podcast/persons.php
+++ b/themes/cp_admin/podcast/persons.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Person.podcast_form.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Person.podcast_form.title') ?> (<?= count($podcast->persons) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('person-create') ?>" variant="primary" iconLeft="add-fill"><?= lang('Person.create') ?></x-Button>
 <?= $this->endSection() ?>
 
@@ -84,7 +80,7 @@
         [
             'header' => lang('Common.actions'),
             'cell'   => function ($person): string {
-                // @icon('delete-bin-fill')
+                // @icon("delete-bin-fill")
                 return '<x-Button uri="' . route_to('podcast-person-remove', $person->podcast_id, $person->id) . '" variant="danger" size="small" iconLeft="delete-bin-fill">' . lang('Person.podcast_form.remove') . '</x-Button>';
             },
         ],
diff --git a/themes/cp_admin/podcast/platforms.php b/themes/cp_admin/podcast/platforms.php
index 1f994bf18f88a351bdc544a9e2b269eb47a99113..d86f2f523c4c130fec26ac2eaf7b731e593f686e 100644
--- a/themes/cp_admin/podcast/platforms.php
+++ b/themes/cp_admin/podcast/platforms.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang("Platforms.title.{$platformType}") ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang("Platforms.title.{$platformType}") ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/publish.php b/themes/cp_admin/podcast/publish.php
index 25f724a3be3b10e6901baa0a7031e783fd7b0df0..f1b91617b97ec8d2b5f42c794a1600da6cb9ca22 100644
--- a/themes/cp_admin/podcast/publish.php
+++ b/themes/cp_admin/podcast/publish.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.publish') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.publish') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/publish_edit.php b/themes/cp_admin/podcast/publish_edit.php
index e24e1d37b92c70d02487b2251b3a0eb3a050fafe..4dd40dc88c9cc29e1840ad0ff8a7255116c577cd 100644
--- a/themes/cp_admin/podcast/publish_edit.php
+++ b/themes/cp_admin/podcast/publish_edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.publish_edit') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.publish_edit') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/settings/dashboard.php b/themes/cp_admin/podcast/settings/dashboard.php
index 87150c4f6d5b10d98b659a9437a58de0633cdb0d..9129f39875323d581b1e9e7be1e76dd5bf949ea2 100644
--- a/themes/cp_admin/podcast/settings/dashboard.php
+++ b/themes/cp_admin/podcast/settings/dashboard.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Podcast.platforms.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Podcast.platforms.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/podcast/view.php b/themes/cp_admin/podcast/view.php
index 04a1156a0754f236db5a9ec8f268c66de7cdcebc..03ccc4ab33dcdbeddec387f1a8cb864463708437 100644
--- a/themes/cp_admin/podcast/view.php
+++ b/themes/cp_admin/podcast/view.php
@@ -1,17 +1,13 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= esc($podcast->title) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= esc($podcast->title) ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
 <?php
-// @icon('pencil-fill')
-// @icon('add-fill')
+// @icon("pencil-fill")
+// @icon("add-fill")
 ?>
 <x-Button uri="<?= route_to('podcast-edit', $podcast->id) ?>" variant="secondary" class="[&>span]:hidden [&>span]:md:block py-3 md:py-2" iconLeft="pencil-fill"><?= lang('Podcast.edit') ?></x-Button>
 <x-Button uri="<?= route_to('episode-create', $podcast->id) ?>" variant="primary" class="[&>span]:hidden [&>span]:md:block py-3 md:py-2" iconLeft="add-fill"><?= lang('Episode.create') ?></x-Button>
diff --git a/themes/cp_admin/settings/about.php b/themes/cp_admin/settings/about.php
index 3c91bfe0e6a025479cc46533b678a32c506c7417..9f9b15fdb79e264fef83596fbe6b0116632ced11 100644
--- a/themes/cp_admin/settings/about.php
+++ b/themes/cp_admin/settings/about.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('AboutCastopod.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('AboutCastopod.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/settings/general.php b/themes/cp_admin/settings/general.php
index 6cdbef938bedb9d689db1297ec66b6758a234a43..5cad5dee6ef45c9950e9e73f5b351b3ec3738302 100644
--- a/themes/cp_admin/settings/general.php
+++ b/themes/cp_admin/settings/general.php
@@ -1,13 +1,5 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Settings.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Settings.title') ?>
 <?= $this->endSection() ?>
@@ -67,7 +59,7 @@
 <x-Forms.Section
     title="<?= lang('Settings.images.title') ?>"
     subtitle="<?= lang('Settings.images.subtitle') ?>">
-    <?php // @icon('refresh-fill')?>
+    <?php // @icon("refresh-fill")?>
     <x-Button variant="primary" type="submit" iconLeft="refresh-fill"><?= lang('Settings.images.regenerate') ?></x-Button>
 
 </x-Forms.Section>
@@ -84,7 +76,7 @@
     <x-Forms.Toggler name="reset_counts" size="small" isChecked="false" hint="<?= esc(lang('Settings.housekeeping.reset_counts_helper')) ?>"><?= lang('Settings.housekeeping.reset_counts') ?></x-Forms.Toggler>
     <x-Forms.Toggler name="rename_episodes_files" size="small" isChecked="false" hint="<?= esc(lang('Settings.housekeeping.rename_episodes_files_hint')) ?>"><?= lang('Settings.housekeeping.rename_episodes_files') ?></x-Forms.Toggler>
     <x-Forms.Toggler name="clear_cache" size="small" isChecked="false" hint="<?= esc(lang('Settings.housekeeping.clear_cache_helper')) ?>"><?= lang('Settings.housekeeping.clear_cache') ?></x-Forms.Toggler>
-    <?php // @icon('home-gear-fill')?>
+    <?php // @icon("home-gear-fill")?>
     <x-Button variant="primary" type="submit" iconLeft="home-gear-fill"><?= lang('Settings.housekeeping.run') ?></x-Button>
 
 </x-Forms.Section>
diff --git a/themes/cp_admin/settings/theme.php b/themes/cp_admin/settings/theme.php
index d01ceb1cd0f60befbbd63b7bfe5baac90802c93d..e0b0612f58c4801edddb5fe06594a721171f4265 100644
--- a/themes/cp_admin/settings/theme.php
+++ b/themes/cp_admin/settings/theme.php
@@ -1,13 +1,5 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Settings.theme.title') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Settings.theme.title') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/subscription/create.php b/themes/cp_admin/subscription/create.php
index c73a091ab51cc1c4f2d260d24e30698c23a04254..31a73a226b59d7be558356486681f571746465e8 100644
--- a/themes/cp_admin/subscription/create.php
+++ b/themes/cp_admin/subscription/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('../cp_admin/_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.add', [esc($podcast->title)]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.add', [esc($podcast->title)]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/subscription/delete.php b/themes/cp_admin/subscription/delete.php
index de0295b4e205aaf0e3e695de394d37c36d06a56a..3926af4490e7c0c4593a1de287e94412044a266d 100644
--- a/themes/cp_admin/subscription/delete.php
+++ b/themes/cp_admin/subscription/delete.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.delete') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.delete') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/subscription/edit.php b/themes/cp_admin/subscription/edit.php
index cd50b7ae08a1366a20b03e0f36c30b618561b5d8..a5ef6ca8f20910bc2772d02b071a5dfff635a217 100644
--- a/themes/cp_admin/subscription/edit.php
+++ b/themes/cp_admin/subscription/edit.php
@@ -1,9 +1,5 @@
 <?= $this->extend('../cp_admin/_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.edit', [esc($podcast->title)]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.edit', [esc($podcast->title)]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/subscription/list.php b/themes/cp_admin/subscription/list.php
index e40fed37762d5d42af798a439fba90370ab3a228..d634a0e218d190d477fcb8d5170cf5600bb781af 100644
--- a/themes/cp_admin/subscription/list.php
+++ b/themes/cp_admin/subscription/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('../cp_admin/_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.podcast_subscriptions') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.podcast_subscriptions') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('add-fill')?>
+<?php // @icon("add-fill")?>
 <x-Button uri="<?= route_to('subscription-create', $podcast->id) ?>" variant="primary" iconLeft="add-fill"><?= lang('Subscription.add') ?></x-Button>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_admin/subscription/suspend.php b/themes/cp_admin/subscription/suspend.php
index b393b06f7e76951e1f2eeebe8dcd481d81aef3e9..6df03d7468d93c17a06ca51c48e1086ba98703f2 100644
--- a/themes/cp_admin/subscription/suspend.php
+++ b/themes/cp_admin/subscription/suspend.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.suspend') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.suspend') ?>
 <?= $this->endSection() ?>
@@ -28,7 +24,7 @@
 
 <div class="flex items-center self-end mt-4 gap-x-2">
     <x-Button uri="<?= route_to('subscription-list', $podcast->id) ?>"><?= lang('Common.cancel') ?></x-Button>
-    <?php // @icon('pause-fill')?>
+    <?php // @icon("pause-fill")?>
     <x-Button type="submit" variant="warning" iconLeft="pause-fill"><?= lang('Subscription.suspend_form.submit') ?></x-Button>
 </div>
 
diff --git a/themes/cp_admin/subscription/view.php b/themes/cp_admin/subscription/view.php
index b17b6acf87da9d4eeea2b844c328ddf276a2afc3..9111b95ac53cb82448af26f4d39984dd28d1ea84 100644
--- a/themes/cp_admin/subscription/view.php
+++ b/themes/cp_admin/subscription/view.php
@@ -1,11 +1,5 @@
 <?= $this->extend('../cp_admin/_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('Subscription.view', [
-    esc($subscription->id),
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('Subscription.view', [
     esc($subscription->id),
diff --git a/themes/cp_admin/user/create.php b/themes/cp_admin/user/create.php
index 269fbbdf28e92de7c9b0a2018cd95627525113a8..4cc5a9f296edcac34c25c18431f17a8c018930fe 100644
--- a/themes/cp_admin/user/create.php
+++ b/themes/cp_admin/user/create.php
@@ -1,9 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('User.create') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('User.create') ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/user/delete.php b/themes/cp_admin/user/delete.php
index 4bee86a1dae47c391277fc7829bbe5b244ebc347..c39490c14f9007d141ce22cb9f310db02c286389 100644
--- a/themes/cp_admin/user/delete.php
+++ b/themes/cp_admin/user/delete.php
@@ -1,11 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('User.delete_form.title', [
-    'user' => $user->username,
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('User.delete_form.title', [
     'user' => $user->username,
diff --git a/themes/cp_admin/user/edit.php b/themes/cp_admin/user/edit.php
index 3ac579675de4ea1b2d0f3b7fdfafd990df171d6e..8631088ea6e06bdf8c72827ad9bdf4bff0e44cd1 100644
--- a/themes/cp_admin/user/edit.php
+++ b/themes/cp_admin/user/edit.php
@@ -1,11 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('User.edit_role', [
-    'username' => esc($user->username),
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('User.edit_role', [
     'username' => esc($user->username),
diff --git a/themes/cp_admin/user/list.php b/themes/cp_admin/user/list.php
index cd9788f18fc6a249d559f670a9f6f370d888b2a3..457c594e842c6a2e919322c53bd5ee3f95e14f85 100644
--- a/themes/cp_admin/user/list.php
+++ b/themes/cp_admin/user/list.php
@@ -1,15 +1,11 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('User.all_users') ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('User.all_users') ?> (<?= count($users) ?>)
 <?= $this->endSection() ?>
 
 <?= $this->section('headerRight') ?>
-<?php // @icon('user-add-fill')?>
+<?php // @icon("user-add-fill")?>
 <x-Button uri="<?= route_to('user-create') ?>" variant="primary" iconLeft="user-add-fill"><?= lang('User.create') ?></x-Button>
 <?= $this->endSection() ?>
 
@@ -37,7 +33,7 @@
                     $role = '<div class="inline-flex items-center"><span class="mr-2" tabindex="0" data-tooltip="bottom" title="' . lang('Auth.instance_groups.owner.title') . '">' . icon('shield-user-fill') . '</span>' . $role . '</div>';
                 }
 
-                // @icon('pencil-fill')
+                // @icon("pencil-fill")
                 return $role . '<x-IconButton uri="' . route_to('user-edit', $user->id) . '" glyph="pencil-fill" variant="info">' . lang('User.edit_role', [
                     'username' => esc($user->username),
                 ]) . '</x-IconButton>';
diff --git a/themes/cp_admin/user/view.php b/themes/cp_admin/user/view.php
index 3ce71ba16d7ecb594381058788437ec6aa863509..199e32984778bf5f104bd0c744d65a11eb486adb 100644
--- a/themes/cp_admin/user/view.php
+++ b/themes/cp_admin/user/view.php
@@ -1,11 +1,5 @@
 <?= $this->extend('_layout') ?>
 
-<?= $this->section('title') ?>
-<?= lang('User.view', [
-    'username' => esc($user->username),
-]) ?>
-<?= $this->endSection() ?>
-
 <?= $this->section('pageTitle') ?>
 <?= lang('User.view', [
     'username' => esc($user->username),
diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php
index f74fb9be14332bba060687ab4393a626960bdc8d..85c36abe59d9b469ba90a872bb60e7fbf0ff1945 100644
--- a/themes/cp_app/embed.php
+++ b/themes/cp_app/embed.php
@@ -33,7 +33,7 @@
             <h1 class="font-semibold leading-tight opacity-100 line-clamp-2 hover:opacity-75"><?= esc($episode->title) ?></h1>
         </a>
         <?php if ($episode->is_premium && ! is_unlocked($podcast->handle)): ?>
-            <?php // @icon('lock-fill')?>
+            <?php // @icon("lock-fill")?>
             <x-Button variant="primary" class="mt-auto mb-2" iconLeft="lock-fill" uri="<?= $episode->link ?>" target="_blank" rel="noopener noreferrer"><?= lang('PremiumPodcasts.unlock') ?></x-Button>
         <?php else: ?>
         <vm-player
diff --git a/themes/cp_app/episode/_layout-preview.php b/themes/cp_app/episode/_layout-preview.php
index efbbe471912fc8e1a14c9b313ad1e27bf5a86f8b..f39178fa8a01c6f95920c3ee8b073b80e16b9ac2 100644
--- a/themes/cp_app/episode/_layout-preview.php
+++ b/themes/cp_app/episode/_layout-preview.php
@@ -170,7 +170,7 @@
         </div>
         <?php if (auth()->loggedIn()): ?>
             <?php if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)): ?>
-                <?php // @icon('upload-cloud-fill')?>
+                <?php // @icon("upload-cloud-fill")?>
                 <x-Button
                     iconLeft="upload-cloud-fill"
                     variant="primary"
@@ -178,7 +178,7 @@
                     class="ml-auto"
                     uri="<?= route_to('episode-publish_edit', $episode->podcast_id, $episode->id) ?>"><?= lang('Episode.preview.publish_edit') ?></x-Button>
             <?php else: ?>
-                <?php // @icon('upload-cloud-fill')?>
+                <?php // @icon("upload-cloud-fill")?>
                 <x-Button
                     iconLeft="upload-cloud-fill"
                     variant="secondary"
diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php
index a74df63bfe8f1d063ee74baa6b6a92d20298877c..27dbc41326ff3ef8423fcf0315502f06fafcc408 100644
--- a/themes/cp_app/episode/_layout.php
+++ b/themes/cp_app/episode/_layout.php
@@ -4,38 +4,15 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/audio-player.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/app.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/podcast.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/audio-player.ts', 'js'))
+?>
 
 <body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <?php if (can_user_interact()): ?>
         <div class="col-span-full">
             <?= $this->include('_admin_navbar') ?>
diff --git a/themes/cp_app/episode/_partials/comment_with_replies.php b/themes/cp_app/episode/_partials/comment_with_replies.php
index 57cc5799ba52673c0e39532b341604352d09d453..6a71a0f96bccf7b549cc6cc95482d993535ad535 100644
--- a/themes/cp_app/episode/_partials/comment_with_replies.php
+++ b/themes/cp_app/episode/_partials/comment_with_replies.php
@@ -27,7 +27,7 @@ if ($comment->in_reply_to_id): ?>
                 'actorUsername' => esc($comment->actor->username),
             ]) ?>"
             rows="1" />
-        <?php // @icon('send-plane-2-fill')?>
+        <?php // @icon("send-plane-2-fill")?>
         <x-Button variant="primary" size="small" type="submit" name="action" value="reply" class="self-end" iconRight="send-plane-2-fill"><?= lang('Comment.form.submit_reply') ?></x-Button>
     </div>
 </form>
diff --git a/themes/cp_app/episode/activity.php b/themes/cp_app/episode/activity.php
index 2a406ee3a6af1752f2888217c441402bf16f82a8..44e2e5c165955f0b485e4929a25569d1cc84303d 100644
--- a/themes/cp_app/episode/activity.php
+++ b/themes/cp_app/episode/activity.php
@@ -17,7 +17,7 @@
                 placeholder="<?= lang('Post.form.episode_message_placeholder') ?>"
                 isRequired="true"
                 rows="2" />
-            <?php // @icon('send-plane-2-fill')?>
+            <?php // @icon("send-plane-2-fill")?>
             <x-Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane-2-fill"><?= lang('Post.form.submit') ?></x-Button>
         </div>
     </form>
diff --git a/themes/cp_app/episode/comments.php b/themes/cp_app/episode/comments.php
index 518d15d0423b4745111f8b00635cc47af855b9da..4c95cd27b85ccd50e0a6a5e4a6591b0e26bca0fc 100644
--- a/themes/cp_app/episode/comments.php
+++ b/themes/cp_app/episode/comments.php
@@ -16,7 +16,7 @@
                 isRequired="true"
                 placeholder="<?= lang('Comment.form.episode_message_placeholder') ?>"
                 rows="2" />
-            <?php // @icon('send-plane-2-fill')?>
+            <?php // @icon("send-plane-2-fill")?>
             <x-Button class="self-end" variant="primary" size="small" type="submit" iconRight="send-plane-2-fill"><?= lang('Comment.form.submit') ?></x-Button>
         </div>
     </form>
diff --git a/themes/cp_app/episode/preview-transcript.php b/themes/cp_app/episode/preview-transcript.php
index c4e421e4ffe3574db3037dde266bda8b4ea21e34..8ccfa07528bf6141ab88947ff0f5abd8b25eeec8 100644
--- a/themes/cp_app/episode/preview-transcript.php
+++ b/themes/cp_app/episode/preview-transcript.php
@@ -4,7 +4,7 @@
 
 <?php if (isset($captions)) : ?>
     <div class="flex flex-col gap-2">
-        <?php // @icon('download-fill')?>
+        <?php // @icon("download-fill")?>
         <x-Button uri="<?= $transcript->file_url ?>" size="small" iconLeft="download-fill" class="self-start" variant="secondary" target="_blank" download="" rel="noopener noreferrer"><?= lang('Episode.download_transcript', [
             'extension' => '.' . $transcript->file_extension,
         ]) ?></x-Button>
diff --git a/themes/cp_app/episode/transcript.php b/themes/cp_app/episode/transcript.php
index a3aef2151b5bb84c586397391e2d4b1b80697aa5..b1701e75aba791b34b5ab85b46e7932acbb38249 100644
--- a/themes/cp_app/episode/transcript.php
+++ b/themes/cp_app/episode/transcript.php
@@ -4,7 +4,7 @@
 
 <?php if (isset($captions)) : ?>
     <div class="flex flex-col gap-2">
-        <?php // @icon('download-fill')?>
+        <?php // @icon("download-fill")?>
         <x-Button uri="<?= $transcript->file_url ?>" size="small" iconLeft="download-fill" class="self-start" variant="secondary" target="_blank" download="" rel="noopener noreferrer"><?= lang('Episode.download_transcript', [
             'extension' => '.' . $transcript->file_extension,
         ]) ?></x-Button>
diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php
index 8021651defbd2a5072f1ef78cf41fb945da8e3cd..ac08f0d256ecb9315a42d37508a2439870c083ba 100644
--- a/themes/cp_app/home.php
+++ b/themes/cp_app/home.php
@@ -3,35 +3,14 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('webmanifest') ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/app.ts', 'js'))
+?>
 
 
 <body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <?php if (auth()->loggedIn()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
@@ -40,10 +19,7 @@
         <h1 class="container flex items-center justify-between px-2 py-4 mx-auto">
             <a href="<?= route_to(
                 'home',
-            ) ?>" class="inline-flex items-baseline text-3xl font-semibold font-display"><?= service('settings')
-            ->get('App.siteName') === 'Castopod' ? 'castopod' .
-    svg('castopod-logo-base', 'h-6 ml-2') : esc(service('settings')
-            ->get('App.siteName')) ?></a>
+            ) ?>" class="inline-flex items-baseline text-3xl font-semibold font-display"><?= service('settings')->get('App.siteName') === 'Castopod' ? 'castopod' . svg('castopod-logo-base', 'h-6 ml-2') : esc(service('settings')->get('App.siteName')) ?></a>
         </h1>
     </header>
     <main class="container flex-1 px-4 py-10 mx-auto">
@@ -52,28 +28,30 @@
                 $podcasts,
             ) ?>)</x-Heading>
             <button class="inline-flex items-center px-2 py-1 text-sm font-semibold" id="sortby-dropdown" data-dropdown="button" data-dropdown-target="sortby-dropdown-menu" aria-haspopup="true" aria-expanded="false"><?= icon('material-symbols:sort', [
-                'class' => 'mr-1 text-xl opacity-50',
-            ]) . lang('Home.sort_by') ?></button>
-            <x-DropdownMenu id="sortby-dropdown-menu" labelledby="sortby-dropdown" items="<?= esc(json_encode([
-                [
-                    'type'  => 'link',
-                    'title' => ($sortBy === 'activity' ? '✓ ' : '') . lang('Home.sort_options.activity'),
-                    'uri'   => route_to('home') . '?sort=activity',
-                    'class' => $sortBy === 'activity' ? 'font-semibold' : '',
-                ],
-                [
-                    'type'  => 'link',
-                    'title' => ($sortBy === 'created_desc' ? '✓ ' : '') . lang('Home.sort_options.created_desc'),
-                    'uri'   => route_to('home') . '?sort=created_desc',
-                    'class' => $sortBy === 'created_desc' ? 'font-semibold' : '',
-                ],
-                [
-                    'type'  => 'link',
-                    'title' => ($sortBy === 'created_asc' ? '✓ ' : '') . lang('Home.sort_options.created_asc'),
-                    'uri'   => route_to('home') . '?sort=created_asc',
-                    'class' => $sortBy === 'created_asc' ? 'font-semibold' : '',
-                ],
-            ])) ?>" />
+                        'class' => 'mr-1 text-xl opacity-50',
+                    ]) . lang('Home.sort_by') ?></button>
+            <x-DropdownMenu id="sortby-dropdown-menu" labelledby="sortby-dropdown" items="<?= esc(
+                json_encode([
+                    [
+                        'type'  => 'link',
+                        'title' => ($sortBy === 'activity' ? '✓ ' : '') . lang('Home.sort_options.activity'),
+                        'uri'   => route_to('home') . '?sort=activity',
+                        'class' => $sortBy === 'activity' ? 'font-semibold' : '',
+                    ],
+                    [
+                        'type'  => 'link',
+                        'title' => ($sortBy === 'created_desc' ? '✓ ' : '') . lang('Home.sort_options.created_desc'),
+                        'uri'   => route_to('home') . '?sort=created_desc',
+                        'class' => $sortBy === 'created_desc' ? 'font-semibold' : '',
+                    ],
+                    [
+                        'type'  => 'link',
+                        'title' => ($sortBy === 'created_asc' ? '✓ ' : '') . lang('Home.sort_options.created_asc'),
+                        'uri'   => route_to('home') . '?sort=created_asc',
+                        'class' => $sortBy === 'created_asc' ? 'font-semibold' : '',
+                    ],
+                ])
+            ) ?>" />
         </div>
         <div class="grid gap-4 mt-4 grid-cols-cards">
             <?php if ($podcasts): ?>
@@ -85,8 +63,8 @@
                                 <?php if ($podcast->is_premium): ?>
                                     <div class="absolute top-0 left-0 z-10 inline-flex items-center mt-2 gap-x-2">
                                         <?= icon('exchange-dollar-fill', [
-                                            'class' => 'w-8 pl-2 text-2xl rounded-r-full rounded-tl-lg text-accent-contrast bg-accent-base',
-                                        ]) ?>
+                                    'class' => 'w-8 pl-2 text-2xl rounded-r-full rounded-tl-lg text-accent-contrast bg-accent-base',
+                                ]) ?>
                                         <?= explicit_badge($podcast->parental_advisory === 'explicit', 'rounded bg-black/75') ?>
                                     </div>
                                 <?php else: ?>
diff --git a/themes/cp_app/pages/_layout.php b/themes/cp_app/pages/_layout.php
deleted file mode 100644
index 14d8b36c361efd4b5c7988e3ce64abc54cb74bd3..0000000000000000000000000000000000000000
--- a/themes/cp_app/pages/_layout.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?= helper('page') ?>
-<!DOCTYPE html>
-<html lang="<?= service('request')
-    ->getLocale() ?>">
-
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('webmanifest') ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/audio-player.ts', 'js') ?>
-</head>
-
-<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
-    <?php if (auth()->loggedIn()): ?>
-        <?= $this->include('_admin_navbar') ?>
-    <?php endif; ?>
-
-    <header class="py-8 border-b bg-elevated border-subtle">
-        <div class="container flex flex-col items-start px-2 py-4 mx-auto">
-            <a href="<?= route_to('home') ?>"
-            class="inline-flex items-center mb-2 text-sm"><?= icon(
-                'arrow-left-line',
-                [
-                    'class' => 'mr-2',
-                ],
-            ) . lang('Page.back_to_home') ?></a>
-            <x-Heading tagName="h1" size="large"><?= esc($page->title) ?></x-Heading>
-        </div>
-    </header>
-    <main class="container flex-1 px-4 py-6 mx-auto">
-        <?= $this->renderSection('content') ?>
-    </main>
-    <footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t border-subtle">
-        <?= render_page_links() ?>
-        <small><?= lang('Common.powered_by', [
-                'castopod' => '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
-            ], null, false) ?></small>
-    </footer>
-</body>
diff --git a/themes/cp_app/pages/credits.php b/themes/cp_app/pages/credits.php
index 0e8b4f232ace69ce12c355cc8df4e18917c5949e..a7eae51148e444fa04f5daaa2c0db014c0578d2c 100644
--- a/themes/cp_app/pages/credits.php
+++ b/themes/cp_app/pages/credits.php
@@ -1,33 +1,51 @@
-<?= $this->extend('pages/_layout') ?>
+<?= helper('page') ?>
+<!DOCTYPE html>
+<html lang="<?= service('request')
+    ->getLocale() ?>" class="h-full">
 
-<?= $this->section('title') ?>
-<?= lang('Person.credits') ?>
-<?= $this->endSection() ?>
+<?= service('html_head')
+    ->title(lang('Person.credits') . service('settings')->get('App.siteTitleSeparator') . service('settings')->get('App.siteName'))
+    ->description(lang('Page.map.description', [
+        'siteName' => esc(service('settings')
+            ->get('App.siteName')),
+    ]))
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+?>
 
-<?= $this->section('content') ?>
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+    ->get('App.theme') ?>">
+    <?php if (auth()->loggedIn()): ?>
+        <?= $this->include('_admin_navbar') ?>
+    <?php endif; ?>
+
+    <header class="py-8 border-b bg-elevated border-subtle">
+        <div class="container flex flex-col items-start px-2 py-4 mx-auto">
+            <a href="<?= route_to('home') ?>"
+            class="inline-flex items-center mb-2 text-sm"><?= icon(
+                'arrow-left-line',
+                [
+                    'class' => 'mr-2',
+                ],
+            ) . lang('Page.back_to_home') ?></a>
+            <x-Heading tagName="h1" size="large"><?= esc($page->title) ?></x-Heading>
+        </div>
+    </header>
+    <main class="container flex-1 px-4 py-6 mx-auto">
 
 <div class="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
     <?php foreach ($credits as $groupSlug => $groups): ?>
         <?php if ($groupSlug): ?>
-            <h2 class="col-span-1 mt-12 mb-2 text-xl font-semibold text-skin-muted md:text-2xl md:col-span-2 "><?= $groups[
-                'group_label'
-            ] ?></h2>
+            <h2 class="col-span-1 mt-12 mb-2 text-xl font-semibold text-skin-muted md:text-2xl md:col-span-2 "><?= $groups['group_label'] ?></h2>
         <?php endif; ?>
         <?php foreach ($groups['persons'] as $persons): ?>
             <div class="flex mt-2 mb-2">
-                <img src="<?= $persons['thumbnail_url'] ?>" alt="<?= esc($persons[
-    'full_name'
-]) ?>" class="object-cover w-16 rounded-full aspect-square md:h-24 md:w-24 border-gray" loading="lazy" />
+                <img src="<?= $persons['thumbnail_url'] ?>" alt="<?= esc($persons['full_name']) ?>" class="object-cover w-16 rounded-full aspect-square md:h-24 md:w-24 border-gray" loading="lazy" />
                 <div class="flex flex-col ml-3 mr-4">
                     <span class="text-lg font-semibold text-skin-muted md:text-xl">
                         <?= esc($persons['full_name']) ?>
                     </span>
                     <?php if ($persons['information_url'] !== null): ?>
-                        <a href="<?= esc($persons[
-                            'information_url'
-                        ]) ?>" class="text-sm font-semibold text-accent-base hover:underline" target="_blank" rel="noreferrer noopener"><?= $persons[
-    'information_url'
-] ?></a>
+                        <a href="<?= esc($persons['information_url']) ?>" class="text-sm font-semibold text-accent-base hover:underline" target="_blank" rel="noreferrer noopener"><?= $persons['information_url'] ?></a>
                     <?php endif; ?>
                 </div>
             </div>
@@ -36,14 +54,18 @@
                     <?= $role['role_label'] ?>
 
                     <?php foreach ($role['is_in'] as $in): ?>
-                        <a href="<?= esc($in[
-                            'link'
-                        ]) ?>" class="text-sm text-skin-muted hover:underline"><?= $in['title'] ?></a>
+                        <a href="<?= esc($in['link']) ?>" class="text-sm text-skin-muted hover:underline"><?= $in['title'] ?></a>
                     <?php endforeach; ?>
                 <?php endforeach; ?>
             </div>
         <?php endforeach; ?>
     <?php endforeach; ?>
 </div>
-
-<?php $this->endSection(); ?>
+</main>
+<footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t border-subtle">
+    <?= render_page_links() ?>
+    <small><?= lang('Common.powered_by', [
+                'castopod' => '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
+            ], null, false) ?></small>
+</footer>
+</body>
diff --git a/themes/cp_app/pages/map.php b/themes/cp_app/pages/map.php
index 526ecbffc052bb14a09e31f31ddcec5967a4c674..50ee885793ff1c76936f44b571b606e540198018 100644
--- a/themes/cp_app/pages/map.php
+++ b/themes/cp_app/pages/map.php
@@ -3,39 +3,19 @@
 <html lang="<?= service('request')
     ->getLocale() ?>" class="h-full">
 
-<head>
-    <meta charset="UTF-8"/>
-    <title><?= lang('Page.map.title') . service('settings')->get('App.siteTitleSeparator') . esc(service('settings')->get('App.siteName')) ?></title>
-    <meta name="description" content="<?= lang('Page.map.description', [
+<?= service('html_head')
+    ->title(lang('Page.map.title') . service('settings')->get('App.siteTitleSeparator') . service('settings')->get('App.siteName'))
+    ->description(lang('Page.map.description', [
         'siteName' => esc(service('settings')
             ->get('App.siteName')),
-    ]) ?>"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('webmanifest') ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/map.ts', 'js') ?>
-</head>
+    ]))
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/app.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/map.ts', 'js'))
+?>
 
 <body class="flex flex-col h-full min-h-screen mx-auto bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <?php if (auth()->loggedIn()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
@@ -58,9 +38,9 @@
     <footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right">
         <?= render_page_links() ?>
         <small><?= lang('Common.powered_by', [
-                'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
-                    'class' => 'ml-1 text-lg',
-                ]) . '</a>',
-            ], null, false) ?></small>
+                    'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
+                        'class' => 'ml-1 text-lg',
+                    ]) . '</a>',
+                ], null, false) ?></small>
     </footer> 
 </body>
diff --git a/themes/cp_app/pages/page.php b/themes/cp_app/pages/page.php
index 430d75941eee2166c2dbbec9953abec9f8393829..a58072f3969be547f9b0b2a26cdf925fd77786d0 100644
--- a/themes/cp_app/pages/page.php
+++ b/themes/cp_app/pages/page.php
@@ -1,11 +1,40 @@
-<?= $this->extend('pages/_layout') ?>
+<?= helper('page') ?>
+<!DOCTYPE html>
+<html lang="<?= service('request')
+    ->getLocale() ?>">
 
-<?= $this->section('title') ?>
-<?= lang('Person.credits') ?>
-<?= $this->endSection() ?>
+<?= service('html_head')
+    ->title($page->title . service('settings')->get('App.siteTitleSeparator') . service('settings')->get('App.siteName'))
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+?>
 
-<?= $this->section('content') ?>
+<body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
+    ->get('App.theme') ?>">
+    <?php if (auth()->loggedIn()): ?>
+        <?= $this->include('_admin_navbar') ?>
+    <?php endif; ?>
+
+    <header class="py-8 border-b bg-elevated border-subtle">
+        <div class="container flex flex-col items-start px-2 py-4 mx-auto">
+            <a href="<?= route_to('home') ?>"
+            class="inline-flex items-center mb-2 text-sm"><?= icon(
+                'arrow-left-line',
+                [
+                    'class' => 'mr-2',
+                ],
+            ) . lang('Page.back_to_home') ?></a>
+            <x-Heading tagName="h1" size="large"><?= esc($page->title) ?></x-Heading>
+        </div>
+    </header>
+    <main class="container flex-1 px-4 py-6 mx-auto">
     <div class="prose prose-brand">
         <?= $page->content_html ?>
     </div>
-<?= $this->endSection() ?>
\ No newline at end of file
+    </main>
+    <footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t border-subtle">
+        <?= render_page_links() ?>
+        <small><?= lang('Common.powered_by', [
+                    'castopod' => '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                ], null, false) ?></small>
+    </footer>
+</body>
diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php
index 7deeb5b00ff747f275042777c4fd603c09863319..130d4150744ad3a74c99f4bd9a894cdba220f2b6 100644
--- a/themes/cp_app/podcast/_layout.php
+++ b/themes/cp_app/podcast/_layout.php
@@ -4,38 +4,15 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/audio-player.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/app.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/podcast.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/audio-player.ts', 'js'))
+?>
 
 <body class="flex flex-col min-h-screen mx-auto md:min-h-full md:grid md:grid-cols-podcast bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <?php if (can_user_interact()): ?>
         <div class="col-span-full">
             <?= $this->include('_admin_navbar') ?>
diff --git a/themes/cp_app/podcast/_partials/premium_banner.php b/themes/cp_app/podcast/_partials/premium_banner.php
index 19301881c434e762d71ca3d33fa5e9dc2c956a88..8999123db89859f38462319a8eb501c1ffa69009 100644
--- a/themes/cp_app/podcast/_partials/premium_banner.php
+++ b/themes/cp_app/podcast/_partials/premium_banner.php
@@ -4,8 +4,8 @@ if ($podcast->is_premium): ?>
     <?php
         $isUnlocked = service('premium_podcasts')
             ->isUnlocked($podcast->handle);
-    // @icon('lock-unlock-fill')
-    // @icon('lock-fill')
+    // @icon("lock-unlock-fill")
+    // @icon("lock-fill")
     $shownIcon = $isUnlocked ? 'lock-unlock-fill' : 'lock-fill';
     $hiddenIcon = $isUnlocked ? 'lock-fill' : 'lock-unlock-fill';
     ?>
@@ -27,7 +27,7 @@ if ($podcast->is_premium): ?>
                     ]) ?>
                     <?= $isUnlocked ? lang('PremiumPodcasts.lock') : lang('PremiumPodcasts.unlock') ?>
                 </x-Button>
-                <?php // @icon('external-link-fill')?>
+                <?php // @icon("external-link-fill")?>
                 <x-Button
                     iconLeft="external-link-fill"
                     target="_blank"
diff --git a/themes/cp_app/podcast/activity.php b/themes/cp_app/podcast/activity.php
index 8d25957f0c6f9ac4933517d0c21a64c9f74ffb9d..db1dbe2fb2108c5e2167c3cd1a06d8068033eb1a 100644
--- a/themes/cp_app/podcast/activity.php
+++ b/themes/cp_app/podcast/activity.php
@@ -21,7 +21,7 @@
             name="episode_url"
             type="url"
             placeholder="<?= lang('Post.form.episode_url_placeholder') . ' (' . lang('Common.optional') . ')' ?>" />
-        <?php // @icon('send-plane-2-fill')?>
+        <?php // @icon("send-plane-2-fill")?>
         <x-Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane-2-fill"><?= lang('Post.form.submit') ?></x-Button>
     </div>
 </form>
diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php
index fff8108bb206a65d36d6d714a22937b406837702..52a3cd5168d28ef45c11cb06eaca29163b134b28 100644
--- a/themes/cp_app/podcast/follow.php
+++ b/themes/cp_app/podcast/follow.php
@@ -4,34 +4,13 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($actor->podcast->handle)) ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/podcast.ts', 'js'))
+?>
 
 <body class="flex flex-col min-h-screen bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <header class="flex flex-col items-center mb-8">
         <h1 class="w-full pt-8 pb-32 text-lg font-semibold text-center text-white bg-header"><?= lang(
             'Fediverse.follow.subtitle',
@@ -60,7 +39,7 @@
                 hint="<?= esc(lang('Fediverse.your_handle_hint')) ?>"
                 isRequired="true"
             />
-            <?php // @icon('send-plane-2-fill')?>
+            <?php // @icon("send-plane-2-fill")?>
             <x-Button variant="primary" type="submit" class="self-end" iconRight="send-plane-2-fill"><?= lang('Fediverse.follow.submit') ?></x-Button>
         </form>
     </main>
@@ -68,9 +47,9 @@
     <footer
         class="flex-col w-full px-2 py-4 mt-auto text-xs text-center border-t text-skin-muted border-subtle">
         <?= lang('Common.powered_by', [
-            'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
-                'class' => 'ml-1 text-lg',
-            ]) . '</a>',
-        ], null, false) ?>
+                'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
+                    'class' => 'ml-1 text-lg',
+                ]) . '</a>',
+            ], null, false) ?>
     </footer>
 </body>
diff --git a/themes/cp_app/podcast/links.php b/themes/cp_app/podcast/links.php
index baf7068744045160e609ea6039dbe53a4fc0a5f4..ca7b1dd82919ed135281393611194b2f0da63903 100644
--- a/themes/cp_app/podcast/links.php
+++ b/themes/cp_app/podcast/links.php
@@ -4,36 +4,14 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/app.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/app.ts', 'js'))
+    ->appendRawContent(service('vite')->asset('js/podcast.ts', 'js'))
+?>
 
 <body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <?php if (can_user_interact()): ?>
         <div class="col-span-full">
             <?= $this->include('_admin_navbar') ?>
diff --git a/themes/cp_app/podcast/unlock.php b/themes/cp_app/podcast/unlock.php
index a86a4b3e10c212c509e9d34aafed44295d9932af..dd50ec5ef30cb224ef33205200bdcfc9a6debbc1 100644
--- a/themes/cp_app/podcast/unlock.php
+++ b/themes/cp_app/podcast/unlock.php
@@ -58,7 +58,7 @@
                 ]) ?>"
                 isRequired="true"
             />
-            <?php // @icon('lock-unlock-fill')?>
+            <?php // @icon("lock-unlock-fill")?>
             <x-Button type="submit" variant="primary" iconLeft="lock-unlock-fill" class="self-center mt-2"><?= lang('PremiumPodcasts.unlock_form.submit') ?></x-Button>
             <?php if ($subscriptionLink = service('settings')
                 ->get('Subscription.link', 'podcast:' . $podcast->id)): ?>
diff --git a/themes/cp_app/post/_partials/actions.php b/themes/cp_app/post/_partials/actions.php
index 67fa56485d09ea157c51633315cc067d2e047846..55a3774d69833f61de32bc6413fe4b761ac9d237 100644
--- a/themes/cp_app/post/_partials/actions.php
+++ b/themes/cp_app/post/_partials/actions.php
@@ -59,7 +59,7 @@
             <form action="<?= route_to(
                 'post-attempt-block-domain',
                 esc(interact_as_actor()
-                ->username),
+                    ->username),
                 $post->id,
             ) ?>" method="POST">
                 <?= csrf_field() ?>
diff --git a/themes/cp_app/post/_partials/post_with_replies.php b/themes/cp_app/post/_partials/post_with_replies.php
index 389f1b51bbdda3778b01987fe22957536f109f63..6586b8c25da8aada49d6c0f8eb86336a41aa77f3 100644
--- a/themes/cp_app/post/_partials/post_with_replies.php
+++ b/themes/cp_app/post/_partials/post_with_replies.php
@@ -32,7 +32,7 @@ if ($post->in_reply_to_id): ?>
                             'actorUsername' => esc($post->actor->username),
                         ]) ?>"
                         rows="1" />
-                    <?php // @icon('send-plane-2-fill')?>
+                    <?php // @icon("send-plane-2-fill")?>
                     <x-Button variant="primary" size="small" type="submit" name="action" value="reply" class="self-end" iconRight="send-plane-2-fill"><?= lang('Post.form.submit_reply') ?></x-Button>
                 </div>
             </form>
diff --git a/themes/cp_app/post/_partials/reply_actions.php b/themes/cp_app/post/_partials/reply_actions.php
index a80c942b8e39d17a527728d929417820d2f0e749..2078efe92678cf16114b9955d5a4f3e44f5e9c7a 100644
--- a/themes/cp_app/post/_partials/reply_actions.php
+++ b/themes/cp_app/post/_partials/reply_actions.php
@@ -58,7 +58,7 @@ if (can_user_interact()): ?>
             <form action="<?= route_to(
                 'post-attempt-block-domain',
                 esc(interact_as_actor()
-                ->username),
+                    ->username),
                 $reply->id,
             ) ?>" method="POST">
                 <?= csrf_field() ?>
diff --git a/themes/cp_app/post/remote_action.php b/themes/cp_app/post/remote_action.php
index 8d76e4930b4e30cdd84f389aff519f8f3f59aff9..5f07da47564658ee4fb5b8a9f8a5188536c42200 100644
--- a/themes/cp_app/post/remote_action.php
+++ b/themes/cp_app/post/remote_action.php
@@ -2,34 +2,13 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" />
-    <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>">
-    <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($post->actor->podcast->handle)) ?>">
-    <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>">
-    <script>
-    // Check that service workers are supported
-    if ('serviceWorker' in navigator) {
-        // Use the window load event to keep the page load performant
-        window.addEventListener('load', () => {
-            navigator.serviceWorker.register('/sw.js');
-        });
-    }
-    </script>
-
-    <?= $metatags ?>
-
-    <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-</head>
+<?= service('html_head')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'))
+    ->appendRawContent(service('vite')->asset('js/podcast.ts', 'js'))
+?>
 
 <body class="flex flex-col min-h-screen mx-auto bg-base theme-<?= service('settings')
-        ->get('App.theme') ?>">
+    ->get('App.theme') ?>">
     <header class="pt-8 pb-32 bg-header">
         <h1 class="text-lg font-semibold text-center text-white"><?= lang(
             'Fediverse.' . $action . '.subtitle',
@@ -37,10 +16,10 @@
     </header>
     <main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24">
         <?= view('post/_partials/card', [
-            'index'   => 1,
-            'podcast' => $podcast,
-            'post'    => $post,
-        ]) ?>
+                'index'   => 1,
+                'podcast' => $podcast,
+                'post'    => $post,
+            ]) ?>
 
         <form action="<?= route_to('post-attempt-remote-action', $post->id, $action) ?>" method="POST" class="flex flex-col mt-8 gap-y-2">
             <?= csrf_field() ?>
@@ -51,16 +30,16 @@
                 label="<?= esc(lang('Fediverse.your_handle')) ?>"
                 hint="<?= esc(lang('Fediverse.your_handle_hint')) ?>"
                 isRequired="true" />
-            <?php // @icon('send-plane-2-fill')?>
+            <?php // @icon("send-plane-2-fill")?>
             <x-Button variant="primary" type="submit" class="self-end" iconRight="send-plane-2-fill"><?= lang('Fediverse.' . $action . '.submit') ?></x-Button>
         </form>
     </main>
     <footer
         class="flex-col w-full px-2 py-4 mt-auto text-xs text-center border-t text-skin-muted border-subtle">
         <?= lang('Common.powered_by', [
-            'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
-                'class' => 'ml-1 text-lg',
-            ]) . '</a>',
-        ], null, false) ?>
+                'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
+                    'class' => 'ml-1 text-lg',
+                ]) . '</a>',
+            ], null, false) ?>
     </footer>
 </body>
diff --git a/themes/cp_auth/_layout.php b/themes/cp_auth/_layout.php
index 490ff9c5e664f5ab9f32c95174c8b63e4bcbe0dc..42876ad20df79a4d4a4d73a0901a97858ae40598 100644
--- a/themes/cp_auth/_layout.php
+++ b/themes/cp_auth/_layout.php
@@ -3,17 +3,11 @@
 <html lang="<?= service('request')
     ->getLocale() ?>">
 
-<head>
-	<meta charset="UTF-8"/>
-	<meta name="robots" content="noindex">
-
-	<title>Castopod Auth</title>
-	<meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/>
-	<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-	<link rel="icon" type="image/x-icon" href="/favicon.ico" />
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-</head>
+<?= service('html_head')
+    ->title('Castopod Auth')
+    ->description('Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.')
+    ->appendRawContent(service('vite')->asset('styles/index.css', 'css'));
+?>
 
 <body class="flex flex-col items-center justify-center min-h-screen mx-auto bg-base">
 	<header class="mb-4">
@@ -25,7 +19,7 @@
 	</header>
 	<main class="flex flex-col w-full max-w-md px-6 py-4 mx-auto rounded-lg bg-elevated border-3 border-subtle gap-y-4">
 		<x-Heading tagName="h1" size="large" class="self-center"><?= $this->renderSection(
-		    'title',
+		    'pageTitle',
 		) ?></x-Heading>
 		<?= view('_message_block') ?>
 		<?= $this->renderSection('content') ?>
@@ -33,9 +27,9 @@
 	<footer class="flex flex-col text-sm">
 		<?= $this->renderSection('footer') ?>
 		<small class="py-4 text-center border-t border-subtle"><?= lang('Common.powered_by', [
-		    'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
-		        'class' => 'ml-1 text-lg',
-		    ]) . '</a>',
-		], null, false) ?></small>
+				    'castopod' => '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social:castopod', [
+				        'class' => 'ml-1 text-lg',
+				    ]) . '</a>',
+				], null, false) ?></small>
 	</footer>
 </body>
diff --git a/themes/cp_auth/email_2fa_show.php b/themes/cp_auth/email_2fa_show.php
index 32e6fdca7d46b92143af1283f7936fafc99eaeb9..3678039debf8d21c6764be43f2eb547454e2c098 100644
--- a/themes/cp_auth/email_2fa_show.php
+++ b/themes/cp_auth/email_2fa_show.php
@@ -1,11 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
-
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.email2FATitle') ?> <?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.email2FATitle') ?> <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
diff --git a/themes/cp_auth/email_2fa_verify.php b/themes/cp_auth/email_2fa_verify.php
index cdfbbbccdd20953c34b155c5918977486b24b5e5..9d761fd1040279ca5cb79280d350fe709ed9dfe2 100644
--- a/themes/cp_auth/email_2fa_verify.php
+++ b/themes/cp_auth/email_2fa_verify.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.emailEnterCode') ?> <?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.emailEnterCode') ?> <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
diff --git a/themes/cp_auth/email_activate_show.php b/themes/cp_auth/email_activate_show.php
index a625eec98d6de6fc6fbed6941e4e2b7ef8e2a01c..85f45849da02452e4035d3122b16d582d1690e9c 100644
--- a/themes/cp_auth/email_activate_show.php
+++ b/themes/cp_auth/email_activate_show.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.emailActivateTitle') ?><?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.emailActivateTitle') ?><?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
diff --git a/themes/cp_auth/login.php b/themes/cp_auth/login.php
index 9d9558fd384d06af550aa3152593f5e11e20f185..e7224eb9376ce378f31dc087a75d4ea4076b3dae 100644
--- a/themes/cp_auth/login.php
+++ b/themes/cp_auth/login.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.login') ?><?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.login') ?><?= $this->endSection() ?>
 
 
 <?= $this->section('content') ?>
diff --git a/themes/cp_auth/magic_link_form.php b/themes/cp_auth/magic_link_form.php
index 4e0b723d03414141261eb751f68501862923731d..0170de009597befff6d4ccf0fa56d86cfec26329 100644
--- a/themes/cp_auth/magic_link_form.php
+++ b/themes/cp_auth/magic_link_form.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.useMagicLink') ?> <?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.useMagicLink') ?> <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
diff --git a/themes/cp_auth/magic_link_message.php b/themes/cp_auth/magic_link_message.php
index 2e406133870d902822114acab08503a0a3db52f6..eed77d74e4b764b51e20b0b462147e187d8e2505 100644
--- a/themes/cp_auth/magic_link_message.php
+++ b/themes/cp_auth/magic_link_message.php
@@ -1,9 +1,6 @@
-<?php declare(strict_types=1);
-
-?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?><?= lang('Auth.useMagicLink') ?> <?= $this->endSection() ?>
+<?= $this->section('pageTitle') ?><?= lang('Auth.useMagicLink') ?> <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
diff --git a/themes/cp_auth/magic_link_set_password.php b/themes/cp_auth/magic_link_set_password.php
index 14703e16f20b2b01f7231467b601e5aa1d68ca7c..9b29aec4fdc052dd12df041fbc634ff5384d095d 100644
--- a/themes/cp_auth/magic_link_set_password.php
+++ b/themes/cp_auth/magic_link_set_password.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?>
+<?= $this->section('pageTitle') ?>
 	<?= lang('Auth.set_password') ?>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_auth/register.php b/themes/cp_auth/register.php
index bd8d9c966bc98740e45efe4d66614f134fa6b561..09dd9d1e13f3f82d403b0464fc04ba1754d82a0b 100644
--- a/themes/cp_auth/register.php
+++ b/themes/cp_auth/register.php
@@ -1,10 +1,7 @@
-<?php declare(strict_types=1);
-
-?>
 <?= helper('form') ?>
 <?= $this->extend(config('Auth')->views['layout']) ?>
 
-<?= $this->section('title') ?>
+<?= $this->section('pageTitle') ?>
 	<?= lang('Auth.register') ?>
 <?= $this->endSection() ?>
 
diff --git a/themes/cp_install/cache_config.php b/themes/cp_install/cache_config.php
index a2198903d9f1930793936f5965caed7bd072e1dd..0ff58fc3d9ab62045f469e8410f9b79c1893cf3a 100644
--- a/themes/cp_install/cache_config.php
+++ b/themes/cp_install/cache_config.php
@@ -27,7 +27,7 @@
         ])) ?>"
     defaultValue="file"
     isRequired="true" />
-<?php // @icon('arrow-right-fill')?>
+<?php // @icon("arrow-right-fill")?>
 <x-Button variant="primary" class="self-end" iconRight="arrow-right-fill" type="submit"><?= lang('Install.form.next') ?></x-Button>
 
 <?= form_close() ?>
diff --git a/themes/cp_install/create_superadmin.php b/themes/cp_install/create_superadmin.php
index 2f51750c306d44511bbd0123aeba3fe7b3290e5c..b27926edba76ae4f3a878aa85769a90e0a33f5cd 100644
--- a/themes/cp_install/create_superadmin.php
+++ b/themes/cp_install/create_superadmin.php
@@ -28,7 +28,7 @@
     type="password"
     isRequired="true"
     autocomplete="new-password" />
-<?php // @icon('check-fill')?>
+<?php // @icon("check-fill")?>
 <x-Button variant="primary" type="submit" class="self-end" iconLeft="check-fill"><?= lang('Install.form.submit') ?></x-Button>
 
 </form>
diff --git a/themes/cp_install/database_config.php b/themes/cp_install/database_config.php
index 76630f392c267acdd42aaabef787a698c6789ca1..d4d30915bbecee1c4c23e67f7b0c20b52b9dafd0 100644
--- a/themes/cp_install/database_config.php
+++ b/themes/cp_install/database_config.php
@@ -1,6 +1,3 @@
-<?php declare(strict_types=1);
-
-?>
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('content') ?>
@@ -54,7 +51,7 @@
     label="<?= esc(lang('Install.form.db_prefix')) ?>"
     hint="<?= esc(lang('Install.form.db_prefix_hint')) ?>"
     value="<?= config('Database')->default['DBPrefix'] ?>" />
-<?php // @icon('arrow-right-fill')?>
+<?php // @icon("arrow-right-fill")?>
 <x-Button variant="primary" type="submit" class="self-end" iconRight="arrow-right-fill"><?= lang('Install.form.next') ?></x-Button>
 
 </form>
diff --git a/themes/cp_install/instance_config.php b/themes/cp_install/instance_config.php
index 78bc32e30f8f9cf8a944200c015b0ea17ba2cb0c..43a48a9b171ae2dc6397b49249dd6f25f26b8ba9 100644
--- a/themes/cp_install/instance_config.php
+++ b/themes/cp_install/instance_config.php
@@ -1,6 +1,3 @@
-<?php declare(strict_types=1);
-
-?>
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('content') ?>
@@ -37,7 +34,7 @@
     hint="<?= esc(lang('Install.form.auth_gateway_hint')) ?>"
     value="<?= config('Auth')->gateway ?>"
     isRequired="true" />
-<?php // @icon('arrow-right-fill')?>
+<?php // @icon("arrow-right-fill")?>
 <x-Button class="self-end" variant="primary" type="submit" iconRight="arrow-right-fill"><?= lang('Install.form.next') ?></x-Button>
 </form>